/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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.civilian.controller; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import org.civilian.Controller; import org.civilian.annotation.Path; import org.civilian.application.ConfigKeys; import org.civilian.controller.classloader.ReloadConfig; import org.civilian.resource.PathParamMap; import org.civilian.type.TypeLib; import org.civilian.util.ArrayUtil; import org.civilian.util.Check; /** * ControllerService is a service to create {@link ControllerType ControllerTypes}. */ public class ControllerService { /** * Creates a new ControllerService. * @param pathParams the PathParams used by an application. Needed to handle * arguments of controller action methods. * @param typeLib the type library used by an application. Needed to handle * arguments of controller action methods. */ public ControllerService(PathParamMap pathParams, TypeLib typeLib, ControllerFactory factory, ReloadConfig reloadConfig) { if (pathParams == null) pathParams = PathParamMap.EMPTY; if (typeLib == null) typeLib = new TypeLib(); loader_ = reloadConfig != null ? new DevLoader(pathParams, typeLib, factory, reloadConfig) : new RealLoader(pathParams, typeLib, factory, getClass().getClassLoader()); } /** * Returns if the service is reloading classes * for each request. To use this feature, develop mode must be true, * and the application needs key {@link ConfigKeys#DEV_CLASSRELOAD} * set to true in its application settings. */ public boolean isReloading() { return loader_.isReloading(); } /** * Returns a ControllerType for a {@link ControllerSignature controller signature}. */ public ControllerType getControllerType(String signature) { return loader_.getControllerType(signature); } /** * CMethods stores the ControllerMethods of a controller. */ private static class CMethods { private static final LeveledMethod[] EMPTY_METHODS = new LeveledMethod[0]; private CMethods(Class<? extends Controller> controllerClass, String methodPath, ControllerFactory factory, int hierarchLevel) { controllerClass_ = Check.notNull(controllerClass, "controllerClass"); hierarchLevel_ = hierarchLevel; methods_ = EMPTY_METHODS; methodPath_ = methodPath; factory_ = factory; } /** * Creates the method list for the Controller base class. */ public CMethods(ControllerFactory factory) { this(Controller.class, null, factory, 0); } /** * Creates an action method list for a controller class. The methods of the controller class are scanned, * all action methods are extracted from the controller class and then joined with all inherited action * methods of the parent list. * @param controllerClass the controller class * @param parentList the method list of the parent class * @param typeLib a type library. */ public CMethods(Class<? extends Controller> controllerClass, String methodPath, CMethods parentList, PathParamMap pathParams, TypeLib typeLib) { this(controllerClass, methodPath, parentList.factory_, parentList.hierarchLevel_ + 1); Check.notNull(parentList, "parentList"); // add inheritable action methods for (LeveledMethod m : parentList.methods_) { if (m.method.canInherit(controllerClass)) methods_ = ArrayUtil.addLast(methods_, m); } // add action methods defined by the controller class MethodArgFactory argFactory = new MethodArgFactory(pathParams, typeLib); int inClassIndex = 0; for (Method javaMethod : controllerClass.getDeclaredMethods()) { Path pathAnno = javaMethod.getAnnotation(Path.class); if (pathAnno == null ? methodPath == null : pathAnno.value().equals(methodPath)) { ControllerMethod method = ControllerMethod.create(argFactory, javaMethod); if (method != null) methods_ = ArrayUtil.addLast(methods_, new LeveledMethod(method, hierarchLevel_, inClassIndex++)); } } Arrays.sort(methods_); } public ControllerType createType() { if (Modifier.isAbstract(controllerClass_.getModifiers())) return null; // can't invoke abstract controllers else { ControllerMethod[] methods = new ControllerMethod[methods_.length]; for (int i=0; i<methods.length; i++) methods[i] = methods_[i].method; return new ControllerType(controllerClass_, methodPath_, factory_, methods); } } public String getSignature() { return ControllerSignature.build(controllerClass_.getName(), methodPath_); } private LeveledMethod[] methods_; private Class<? extends Controller> controllerClass_; private int hierarchLevel_; private String methodPath_; private ControllerFactory factory_; } private static class LeveledMethod implements Comparable<LeveledMethod> { public LeveledMethod(ControllerMethod method, int hierarchyLevel, int inClassIndex) { this.method = method; this.hierarchyLevel = hierarchyLevel; this.inClassIndex = inClassIndex; } @Override public int compareTo(LeveledMethod other) { if (other.hierarchyLevel != hierarchyLevel) return other.hierarchyLevel - hierarchyLevel; else return other.inClassIndex - inClassIndex; } @Override public int hashCode() { return method.hashCode(); } @Override public boolean equals(Object other) { return (other instanceof LeveledMethod) && (((LeveledMethod)other).method == method); } public final ControllerMethod method; public final int hierarchyLevel; public final int inClassIndex; } /** * Loader implements ControllerService.getControllerType(String signature). */ private abstract static class Loader { public abstract ControllerType getControllerType(String signature); public abstract boolean isReloading(); } /** * DevLoader is a Loader for development mode with controller class reloading turned on. */ private static class DevLoader extends Loader { public DevLoader(PathParamMap pathParams, TypeLib typeLib, ControllerFactory factory, ReloadConfig reloadConfig) { pathParams_ = pathParams; typeLib_ = typeLib; factory_ = factory; reloadConfig_ = reloadConfig; } @Override public boolean isReloading() { return true; } @Override public ControllerType getControllerType(String signature) { RealLoader loader = new RealLoader(pathParams_, typeLib_, factory_, reloadConfig_.createClassLoader()); return loader.getControllerType(signature); } private PathParamMap pathParams_; private TypeLib typeLib_; private ControllerFactory factory_; private ReloadConfig reloadConfig_; } /** * RealLoader is a Loader which implements loading of ControllerTypes. * ControllerTypes are cached. */ private static class RealLoader extends Loader { public RealLoader(PathParamMap pathParams, TypeLib typeLib, ControllerFactory factory, ClassLoader classLoader) { pathParams_ = pathParams; typeLib_ = typeLib; classLoader_ = classLoader; addMethods(new CMethods(factory)); } @Override public boolean isReloading() { return false; } private void addMethods(CMethods methods) { signature2methods_.put(methods.getSignature(), methods); } @Override public synchronized ControllerType getControllerType(String signature) { if (signature != null) { CMethods methods = getMethods(signature); if (methods != null) return methods.createType(); } return null; } /** * Returns the controller methods for a controller class. */ private CMethods getMethods(String signature) { CMethods methods = signature2methods_.get(signature); if (methods == null) { String parts[] = ControllerSignature.parse(signature); methods = findMethods(constructControllerClass(parts[0]), parts[1]); } return methods; } private Class<? extends Controller> constructControllerClass(String name) { try { Class<?> c = Class.forName(name, false, classLoader_); return checkClass(c); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("class '" + name + "' not found", e); } } private CMethods findMethods(Class<? extends Controller> controllerClass, String methodPath) { Class<? extends Controller> superClass = checkClass(controllerClass.getSuperclass()); CMethods parentMethods = getMethods(superClass.getName()); CMethods methods = new CMethods(controllerClass, methodPath, parentMethods, pathParams_, typeLib_); addMethods(methods); return methods; } @SuppressWarnings("unchecked") private Class<? extends Controller> checkClass(Class<?> c) { if (!Controller.class.isAssignableFrom(c)) throw new IllegalStateException("'" + c.getName() + "' is not a controller class: maybe there is a classloader problem?"); return (Class<? extends Controller>)c; } private PathParamMap pathParams_; private TypeLib typeLib_; private ClassLoader classLoader_; private HashMap<String, CMethods> signature2methods_ = new HashMap<>(); } @Override public String toString() { return getClass().getSimpleName(); } private Loader loader_; }