DS-Lab / src / main / java / dslab / protocol / ProtocolException.java
ProtocolException.java
Raw
package dslab.protocol;

import dslab.authentication.AuthenticationException;
import dslab.mailbox.MessageNotFoundException;
import dslab.protocol.dmtp.InvalidMessageException;
import dslab.protocol.dmtp.NoOpenMessageException;
import dslab.rmi.serialize.IllegalCommandException;
import dslab.rmi.serialize.MissingArgumentException;
import dslab.rmi.serialize.ParseException;
import dslab.routing.AddressResolutionException;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * The business code equivalent to a DMTP/DMAP response of kind "error ...".
 * The code is meant to be encoded into the protocol response. It should identify the message
 * so it can be deserialized at the client and must not contain any spaces
 */
public abstract class ProtocolException extends Exception {

    private static final Logger LOG = Logger.getLogger(ProtocolException.class.getSimpleName());
    private static final Map<String, Class<? extends ProtocolException>> code_exception;

    static {
        code_exception = new ConcurrentHashMap<>();

        List.of(
                AuthenticationException.class,
                InvalidMessageException.class,
                NoOpenMessageException.class,
                IllegalCommandException.class,
                MissingArgumentException.class,
                ParseException.class,
                MessageNotFoundException.class
        ).forEach(ProtocolException::registerStringRepresentationForException);

        registerStringRepresentationForException("unknown", AddressResolutionException.class);
    }

    /**
     * When exceptions occur during communication via DMTP or DMAP, the exception is encoded as a string
     * in the format "error <exception string> <exception message>"
     * For example, the client might try to edit a message before sending "begin", which causes a
     * NoOpenMessageException to occur. The server answers
     * "error NoOpenMessageException begin message first".
     * <p>
     * We can't always use the exceptions class name as its string representation because
     * in some cases the DMTP spec requires responses different from the exception name, i.e.
     * "error unknown Invalid Credentials" for an AddressResolutionException.
     */
    protected static void registerStringRepresentationForException(String stringRepresentation,
                                                                   Class<? extends ProtocolException> exceptionClass) {
        code_exception.put(stringRepresentation, exceptionClass);
    }

    protected static void registerStringRepresentationForException(Class<? extends ProtocolException> exceptionClass) {
        registerStringRepresentationForException(exceptionClass.getSimpleName().toLowerCase(), exceptionClass);
    }

    private String message = "";

    public ProtocolException() {super();}

    public ProtocolException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public String getCode() {
        return this.getClass().getSimpleName().toLowerCase();
    }

    public ProtocolException(String message, Exception cause) {super(message, cause);}

    public static ProtocolException from(String code, String message) {

        try {
            Class<? extends Exception> exceptionClass = code_exception
                    .entrySet()
                    .stream()
                    .filter(entry -> entry.getKey().equalsIgnoreCase(code))
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException(
                            "Didn't find a class for code '" + code + "'. " +
                                    "Did you register the exception?"))
                    .getValue();

            ProtocolException protocolException;

            try { //if a no-arg constructor exists we use that one.
                protocolException = (ProtocolException) exceptionClass.getConstructor().newInstance();
                return protocolException;
            } catch (NoSuchMethodException ignore){
                LOG.finer("No no-arg constructor found");
            }

            //else try the constructor with one message parameter
            protocolException = (ProtocolException) exceptionClass.getConstructor(String.class).newInstance(message);
            return protocolException;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void setMessage(String message) {
        this.message = message;
    }
}