All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rapidoid.dispatch.impl.PojoDispatcherImpl Maven / Gradle / Ivy

The newest version!
package org.rapidoid.dispatch.impl;

/*
 * #%L
 * rapidoid-dispatch
 * %%
 * Copyright (C) 2014 - 2015 Nikolche Mihajlovski and contributors
 * %%
 * Licensed 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.
 * #L%
 */

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Param;
import org.rapidoid.annotation.Since;
import org.rapidoid.aop.AOP;
import org.rapidoid.beany.Beany;
import org.rapidoid.beany.Metadata;
import org.rapidoid.cls.Cls;
import org.rapidoid.cls.TypeKind;
import org.rapidoid.dispatch.DispatchResult;
import org.rapidoid.dispatch.PojoDispatchException;
import org.rapidoid.dispatch.PojoDispatcher;
import org.rapidoid.dispatch.PojoHandlerNotFoundException;
import org.rapidoid.dispatch.PojoRequest;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;
import org.rapidoid.util.Constants;
import org.rapidoid.wire.Wire;

@Authors("Nikolche Mihajlovski")
@Since("2.0.0")
public class PojoDispatcherImpl implements PojoDispatcher, Constants {

	private static final String[] NO_PARTS = {};

	protected final Map mappings = U.map();

	public PojoDispatcherImpl(Map> components) {
		if (components != null) {
			init(components);
		}
	}

	private void init(Map> components) {
		for (Class component : components.values()) {
			if (component.getCanonicalName() != null && !component.getCanonicalName().startsWith("org.rapidoid.log.")) {

				Log.debug("Processing POJO", "class", component);

				List componentPaths = getComponentNames(component);
				for (String componentPath : componentPaths) {

					for (Method method : component.getMethods()) {
						if (shouldExpose(method)) {
							List actions = getMethodActions(componentPath, method);

							for (DispatchReq action : actions) {
								mappings.put(action, new DispatchTarget(component, method, action.config));
								Log.info("Registered web handler", "kind", action.kind, "command", action.command,
										"path", action.path, "method", method);
							}
						}
					}
				}
			}
		}
	}

	protected List getComponentNames(Class component) {
		return U.list(component.getSimpleName());
	}

	@Override
	public DispatchResult dispatch(PojoRequest req) throws PojoHandlerNotFoundException, PojoDispatchException {
		return process(req, req.command(), req.path(), NO_PARTS, 0);
	}

	protected DispatchResult process(PojoRequest req, String command, String path, String[] parts, int paramsFrom)
			throws PojoHandlerNotFoundException, PojoDispatchException {

		// normalize the path
		path = U.uri(path);

		if (req.isEvent()) {
			// find the event handler
			DispatchTarget target = mappings.get(new DispatchReq(command, path, DispatchReqKind.EVENT, null));

			if (target != null) {
				return call(req, parts, paramsFrom, target, DispatchReqKind.EVENT);
			}

		} else {

			// try a service
			DispatchTarget target = mappings.get(new DispatchReq(command, path, DispatchReqKind.SERVICE, null));
			boolean isService = target != null;

			if (!isService) {
				// try a web page
				target = mappings.get(new DispatchReq(command, path, DispatchReqKind.PAGE, null));
			}

			if (target != null) {
				return call(req, parts, paramsFrom, target, isService ? DispatchReqKind.SERVICE : DispatchReqKind.PAGE);
			}
		}

		throw notFound();
	}

	private DispatchResult call(PojoRequest req, String[] parts, int paramsFrom, DispatchTarget target,
			DispatchReqKind kind) throws PojoHandlerNotFoundException, PojoDispatchException {

		if (target.method != null) {
			Object componentInstance = Wire.singleton(target.clazz);
			Object callResult = doDispatch(req, target.method, componentInstance, parts, paramsFrom);
			return new DispatchResult(callResult, kind, target.config);
		} else {
			throw notFound();
		}
	}

	private Object doDispatch(PojoRequest request, Method method, Object component, String[] parts, int paramsFrom)
			throws PojoHandlerNotFoundException, PojoDispatchException {

		Object[] args = setupArgs(request, method, component, parts, paramsFrom);

		preprocess(request, method, component, args);
		Object result = invoke(request, method, component, args);
		result = postprocess(request, method, component, args, result);

		return result;
	}

	protected Object invoke(PojoRequest req, Method method, Object component, Object[] args) {
		return AOP.invoke(null, method, component, args);
	}

	private Object[] setupArgs(PojoRequest request, Method method, Object component, String[] parts, int paramsFrom)
			throws PojoDispatchException {

		Object[] args;

		try {
			int paramsSize = parts.length - paramsFrom;
			Class[] types = method.getParameterTypes();
			Annotation[][] annotations = method.getParameterAnnotations();
			args = new Object[types.length];

			int subUrlParamIndex = 0;

			for (int i = 0; i < types.length; i++) {
				Class type = types[i];
				TypeKind kind = Cls.kindOf(type);

				if (kind.isSimple()) {

					Param param = Metadata.get(annotations[i], Param.class);

					if (param != null) {
						String paramName = param.value();
						Object val = request.param(paramName);
						args[i] = Cls.convert(val, type);
					} else if (isCustomSimpleArg(request, annotations[i])) {
						args[i] = Cls.convert(customSimpleArg(request, annotations[i]), type);
					} else {

						if (args[i] == null) {
							if (parts.length > paramsFrom + subUrlParamIndex) {
								args[i] = Cls.convert(parts[paramsFrom + subUrlParamIndex++], type);
							} else {
								throw error(null, "Not enough parameters!");
							}
						}
					}

				} else if (type.equals(Object.class)) {
					Class defaultType = getDefaultType(component);

					if (defaultType != null) {
						args[i] = instantiateArg(request, defaultType);
					} else {
						throw error(null, "Cannot provide value for parameter of type Object!");
					}
				} else {
					args[i] = complexArg(i, type, request, parts, paramsFrom, paramsSize);
				}
			}
		} catch (Throwable e) {
			throw new PojoDispatchException("Cannot dispatch to POJO target!", e);
		}

		return args;
	}

	protected boolean isCustomSimpleArg(PojoRequest request, Annotation[] annotations) {
		return false;
	}

	protected Object customSimpleArg(PojoRequest request, Annotation[] annotations) {
		return null;
	}

	protected void preprocess(PojoRequest request, Method method, Object component, Object[] args) {}

	protected Object postprocess(PojoRequest request, Method method, Object component, Object[] args, Object result) {
		if (result == null && method.getReturnType().equals(void.class)) {
			result = "OK";
		}
		return result;
	}

	protected Object complexArg(int i, Class type, PojoRequest request, String[] parts, int paramsFrom,
			int paramsSize) throws PojoDispatchException {

		if (type.equals(Map.class)) {
			return mapArg(request, parts, paramsFrom);
		} else if (type.equals(String[].class)) {
			return stringsArg(request, parts, paramsFrom, paramsSize);
		} else if (type.equals(List.class) || type.equals(Collection.class)) {
			return listArg(request, parts, paramsFrom, paramsSize);
		} else if (type.equals(Set.class)) {
			return setArg(request, parts, paramsFrom, paramsSize);
		} else if (isCustomType(type)) {
			return getCustomArg(request, type, parts, paramsFrom, paramsSize);
		} else if (type.getCanonicalName().startsWith("java")) {
			throw error(null, "Parameter type '%s' is not supported!", type.getCanonicalName());
		} else {
			return instantiateArg(request, type);
		}
	}

	private Class getDefaultType(Object component) {
		Object clazz = Beany.getPropValue(component, "clazz");
		return (Class) (clazz instanceof Class ? clazz : null);
	}

	private Object instantiateArg(PojoRequest request, Class type) throws PojoDispatchException {
		try {
			Constructor constructor = type.getConstructor();

			try {
				Object instance = constructor.newInstance();
				Beany.update(instance, request.params());
				return instance;

			} catch (Exception e) {
				throw error(e, "Cannot create a new instance of type: '%s'!", type.getCanonicalName());
			}

		} catch (NoSuchMethodException e) {
			throw error(e, "Cannot find a constructor with 0 parameters for type '%s'!", type.getCanonicalName());

		} catch (SecurityException e) {
			throw error(e, "Cannot retrieve the constructor with 0 parameters for type '%s'!", type.getCanonicalName());
		}
	}

	protected Object getCustomArg(PojoRequest request, Class type, String[] parts, int paramsFrom, int paramsSize) {
		return null;
	}

	protected boolean isCustomType(Class type) {
		return false;
	}

	private Set setArg(PojoRequest request, String[] parts, int paramsFrom, int paramsSize) {
		if (parts.length > paramsFrom) {
			Set arguments = U.set();

			for (int j = paramsFrom; j < parts.length; j++) {
				arguments.add(parts[j]);
			}
			return arguments;
		} else {
			return U.set();
		}
	}

	private List listArg(PojoRequest request, String[] parts, int paramsFrom, int paramsSize) {
		if (parts.length > paramsFrom) {
			List arguments = new ArrayList(paramsSize);

			for (int j = paramsFrom; j < parts.length; j++) {
				arguments.add(parts[j]);
			}

			return arguments;
		} else {
			return U.list();
		}
	}

	private String[] stringsArg(PojoRequest request, String[] parts, int paramsFrom, int paramsSize) {
		if (parts.length > paramsFrom) {
			String[] arguments = new String[paramsSize];
			System.arraycopy(parts, paramsFrom, arguments, 0, paramsSize);
			return arguments;
		} else {
			return EMPTY_STRING_ARRAY;
		}
	}

	private Map mapArg(PojoRequest request, String[] parts, int paramsFrom) {
		Map params = request.params();

		for (int j = paramsFrom; j < parts.length; j++) {
			params.put("" + (j - paramsFrom + 1), parts[j]);
		}

		return params;
	}

	@SuppressWarnings("unchecked")
	protected List getMethodActions(String componentPath, Method method) {
		String path = U.uri(componentPath, method.getName());
		return U.list(new DispatchReq("", path, DispatchReqKind.SERVICE, Collections.EMPTY_MAP));
	}

	private boolean shouldExpose(Method method) {
		boolean isUserDefined = !method.getDeclaringClass().equals(Object.class);

		int modifiers = method.getModifiers();
		boolean isPublic = !Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers);

		return isUserDefined && isPublic;
	}

	protected static PojoDispatchException error(Throwable cause, String msg, Object... args) {
		return new PojoDispatchException(U.frmt(msg, args), cause);
	}

	protected static PojoHandlerNotFoundException notFound() {
		return new PojoHandlerNotFoundException();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy