blob: 0c9e4b29ce033cf970446f7fe4e80fd40b6c0f5a [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.protocols.api.handler;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.apache.james.protocols.api.BaseRequest;
import org.apache.james.protocols.api.ProtocolSession;
import org.apache.james.protocols.api.Request;
import org.apache.james.protocols.api.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A CommandDispatcher is responsible to call the right {@link CommandHandler} for a given Command
*
*/
public class CommandDispatcher<SessionT extends ProtocolSession> implements ExtensibleHandler, LineHandler<SessionT> {
private static final Logger LOGGER = LoggerFactory.getLogger(CommandDispatcher.class);
/**
* The list of available command handlers
*/
private final HashMap<String, List<CommandHandler<SessionT>>> commandHandlerMap = new HashMap<>();
private final List<ProtocolHandlerResultHandler<Response, SessionT>> rHandlers = new ArrayList<>();
private final Collection<String> mandatoryCommands;
public CommandDispatcher(Collection<String> mandatoryCommands) {
this.mandatoryCommands = mandatoryCommands;
}
public CommandDispatcher() {
this(Collections.<String>emptyList());
}
/**
* Add it to map (key as command name, value is an array list of CommandHandlers)
*
* @param commandName the command name which will be key
* @param cmdHandler The CommandHandler object
*/
protected void addToMap(String commandName, CommandHandler<SessionT> cmdHandler) {
List<CommandHandler<SessionT>> handlers = commandHandlerMap.get(commandName);
if (handlers == null) {
handlers = new ArrayList<>();
commandHandlerMap.put(commandName, handlers);
}
handlers.add(cmdHandler);
}
/**
* Returns all the configured CommandHandlers for the specified command
*
* @param command the command name which will be key
* @param session not null
* @return List of CommandHandlers
*/
protected List<CommandHandler<SessionT>> getCommandHandlers(String command, ProtocolSession session) {
if (command == null) {
return null;
}
LOGGER.debug("Lookup command handler for command: {}", command);
List<CommandHandler<SessionT>> handlers = commandHandlerMap.get(command);
if (handlers == null) {
handlers = commandHandlerMap.get(getUnknownCommandHandlerIdentifier());
}
return handlers;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void wireExtensions(Class interfaceName, List extensions) throws WiringException {
if (interfaceName.equals(ProtocolHandlerResultHandler.class)) {
rHandlers.addAll(extensions);
}
if (interfaceName.equals(CommandHandler.class)) {
for (Object extension : extensions) {
CommandHandler handler = (CommandHandler) extension;
Collection implCmds = handler.getImplCommands();
for (Object implCmd : implCmds) {
String commandName = ((String) implCmd).trim().toUpperCase(Locale.US);
addToMap(commandName, handler);
}
}
if (commandHandlerMap.size() < 1) {
throw new WiringException("No commandhandlers configured");
} else {
for (String cmd: mandatoryCommands) {
if (!commandHandlerMap.containsKey(cmd)) {
throw new WiringException("No commandhandlers configured for mandatory command " + cmd);
}
}
}
}
}
@Override
public Response onLine(SessionT session, ByteBuffer line) {
Request request;
try {
request = parseRequest(session, line);
if (request == null) {
return null;
}
} catch (Exception e) {
LOGGER.debug("Unable to parse request", e);
return session.newFatalErrorResponse();
}
try {
return dispatchCommandHandlers(session, request);
} catch (Exception e) {
LOGGER.error("Error dispatching command for request {}", request.getCommand(), e);
return session.newFatalErrorResponse();
}
}
/**
* Dispatch the {@link CommandHandler}'s for the given {@link Request} and return a {@link Response} or <code>null</code> if non should get written
* back to the client
* @return response
*/
protected Response dispatchCommandHandlers(SessionT session, Request request) {
LOGGER.debug("{} received: {}", getClass().getName(), request.getCommand());
List<CommandHandler<SessionT>> commandHandlers = getCommandHandlers(request.getCommand(), session);
// fetch the command handlers registered to the command
if (commandHandlers == null) {
return session.newCommandNotFoundErrorResponse();
}
for (CommandHandler<SessionT> commandHandler : commandHandlers) {
final long start = System.currentTimeMillis();
Response response = commandHandler.onCommand(session, request);
if (response != null) {
long executionTime = System.currentTimeMillis() - start;
// now process the result handlers
response = executeResultHandlers(session, response, executionTime, commandHandler, rHandlers.iterator());
if (response != null) {
return response;
}
}
}
return null;
}
private Response executeResultHandlers(final SessionT session, Response responseFuture, final long executionTime, final CommandHandler<SessionT> cHandler, final Iterator<ProtocolHandlerResultHandler<Response, SessionT>> resultHandlers) {
// Check if the there is a ResultHandler left to execute if not just return the response
if (resultHandlers.hasNext()) {
responseFuture = resultHandlers.next().onResponse(session, responseFuture, executionTime, cHandler);
// call the next ResultHandler
return executeResultHandlers(session, responseFuture, executionTime, cHandler, resultHandlers);
}
return responseFuture;
}
/**
* Parse the line into a {@link Request}
*/
protected Request parseRequest(SessionT session, ByteBuffer buffer) throws Exception {
String curCommandName;
String curCommandArgument = null;
byte[] line;
if (buffer.hasArray()) {
line = buffer.array();
} else {
line = new byte[buffer.remaining()];
buffer.get(line);
}
// This should be changed once we move to java6
String cmdString = new String(line, session.getCharset().name()).trim();
int spaceIndex = cmdString.indexOf(" ");
if (spaceIndex > 0) {
curCommandName = cmdString.substring(0, spaceIndex);
curCommandArgument = cmdString.substring(spaceIndex + 1);
} else {
curCommandName = cmdString;
}
curCommandName = curCommandName.toUpperCase(Locale.US);
return new BaseRequest(curCommandName, curCommandArgument);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Class<?>> getMarkerInterfaces() {
List res = new LinkedList();
res.add(CommandHandler.class);
res.add(ProtocolHandlerResultHandler.class);
return res;
}
/**
* Return the identifier to lookup the UnknownCmdHandler in the handler map
*
* @return identifier
*/
protected String getUnknownCommandHandlerIdentifier() {
return UnknownCommandHandler.COMMAND_IDENTIFIER;
}
}