/* * Copyright 2013 eXo Platform SAS * * 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. */ package juzu.impl.plugin.controller.metamodel; import juzu.Action; import juzu.Application; import juzu.Consumes; import juzu.Resource; import juzu.View; import juzu.impl.common.Name; import juzu.impl.plugin.application.metamodel.ApplicationMetaModel; import juzu.impl.plugin.application.metamodel.ApplicationMetaModelPlugin; import juzu.impl.metamodel.AnnotationKey; import juzu.impl.metamodel.AnnotationState; import juzu.impl.compiler.ProcessingException; import juzu.impl.compiler.ElementHandle; import juzu.impl.compiler.ProcessingContext; import juzu.impl.plugin.module.metamodel.ModuleMetaModel; import juzu.impl.request.BeanParameter; import juzu.impl.request.ContextualParameter; import juzu.impl.request.ControlParameter; import juzu.impl.request.ControllerHandler; import juzu.impl.request.PhaseParameter; import juzu.impl.plugin.controller.descriptor.ControllerDescriptor; import juzu.impl.metamodel.MetaModelEvent; import juzu.impl.metamodel.MetaModelObject; import juzu.impl.request.Request; import juzu.impl.common.Cardinality; import juzu.impl.common.JSON; import juzu.impl.common.Tools; import juzu.impl.value.ValueType; import juzu.processor.MainProcessor; import juzu.request.Phase; import javax.annotation.Generated; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.JavaFileObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ public class ControllerMetaModelPlugin extends ApplicationMetaModelPlugin { /** . */ private static final String METHOD_DESCRIPTOR = ControllerHandler.class.getSimpleName(); /** . */ private static final String CONTROLLER_DESCRIPTOR = ControllerDescriptor.class.getSimpleName(); /** . */ private static final String PARAMETER = ControlParameter.class.getSimpleName(); /** . */ private static final String PHASE_PARAMETER = PhaseParameter.class.getSimpleName(); /** . */ private static final String CONTEXTUAL_PARAMETER = ContextualParameter.class.getSimpleName(); /** . */ private static final String BEAN_PARAMETER = BeanParameter.class.getSimpleName(); /** . */ private static final String PHASE = Phase.class.getSimpleName(); /** . */ private static final String TOOLS = Tools.class.getSimpleName(); /** . */ public static final String CARDINALITY = Cardinality.class.getSimpleName(); /** . */ private HashSet<ControllerMetaModel> written = new HashSet<ControllerMetaModel>(); /** . */ private static final String SERVICES = "META-INF/services/" + ValueType.class.getName(); /** . */ final HashSet<ElementHandle.Type> valueTypes = new HashSet<ElementHandle.Type>(); public ControllerMetaModelPlugin() { super("controller"); } public Set<Class<? extends java.lang.annotation.Annotation>> init(ProcessingContext env) { return Tools.<Class<? extends java.lang.annotation.Annotation>>set(View.class, Action.class, Consumes.class, Resource.class); } @Override public void postActivate(ModuleMetaModel applications) { valueTypes.clear(); for (ValueType<?> valueType : ValueType.DEFAULT) { for (Class<?> type : valueType.getTypes()) { valueTypes.add(ElementHandle.Type.create(Name.create(type))); } } Enumeration<URL> services; try { services = MainProcessor.class.getClassLoader().getResources(SERVICES); } catch (IOException e) { services = null; } if (services != null) { while (services.hasMoreElements()) { URL url = services.nextElement(); try { InputStream in = url.openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in, Tools.UTF_8)); while (true) { String line = reader.readLine(); if (line != null) { String providerName = line.trim(); TypeElement provider = applications.getProcessingContext().getTypeElement(providerName); if (provider.getKind() == ElementKind.CLASS) { List<? extends TypeMirror> superTypes = applications.getProcessingContext().directSupertypes((TypeMirror)provider.asType()); if (superTypes.size() > 0) { TypeMirror superType = superTypes.get(0); if (superType.getKind() == TypeKind.DECLARED) { DeclaredType declaredTypeSuper = (DeclaredType)superType; TypeElement declaredSuperElement = (TypeElement)declaredTypeSuper.asElement(); if (ValueType.class.getName().equals(declaredSuperElement.getQualifiedName().toString())) { TypeMirror argument = declaredTypeSuper.getTypeArguments().get(0); if (argument instanceof DeclaredType) { DeclaredType declaredArgumentType = (DeclaredType)argument; Element declaredArgumentElement = declaredArgumentType.asElement(); if (declaredArgumentElement instanceof TypeElement) { // Here we are ElementHandle.Type valueType = ElementHandle.Type.create((TypeElement) declaredArgumentElement); valueTypes.add(valueType); } } } else { // ? } } else { // ? } } else { // This is likely an interface } } } else { break; } } } catch (IOException e) { // ? } } } } @Override public void init(ApplicationMetaModel application) { ControllersMetaModel controllers = new ControllersMetaModel(this); PackageElement pkg = application.model.processingContext.get(application.getHandle()); AnnotationMirror annotation = Tools.getAnnotation(pkg, Application.class.getName()); AnnotationState values = AnnotationState.create(annotation); Boolean escapeXML = (Boolean)values.get("escapeXML"); ElementHandle.Type defaultControllerElt = (ElementHandle.Type)values.get("defaultController"); ElementHandle.Type errorControllerElt = (ElementHandle.Type)values.get("errorController"); controllers.escapeXML = escapeXML; controllers.defaultController = defaultControllerElt != null ? defaultControllerElt.getName() : null; controllers.errorController = errorControllerElt != null ? errorControllerElt.getName() : null; application.addChild(ControllersMetaModel.KEY, controllers); } @Override public void processAnnotationAdded(ApplicationMetaModel application, AnnotationKey key, AnnotationState added) { ElementHandle.Method methodHandle = (ElementHandle.Method)key.getElement(); ElementHandle.Type controllerHandle = methodHandle.getType(); ControllersMetaModel controllers = application.getChild(ControllersMetaModel.KEY); ControllerMetaModel controller = controllers.get(controllerHandle); if (controller == null) { controllers.add(controller = new ControllerMetaModel(controllerHandle)); } controller.addMethod(application.model, key, added); } @Override public void processAnnotationRemoved(ApplicationMetaModel metaModel, AnnotationKey key, AnnotationState removed) { ElementHandle.Method methodHandle = (ElementHandle.Method)key.getElement(); ElementHandle.Type controllerHandle = ElementHandle.Type.create(methodHandle.getTypeName()); ControllersMetaModel controllers = metaModel.getChild(ControllersMetaModel.KEY); ControllerMetaModel controller = controllers.get(controllerHandle); if (controller != null) { controller.removeMethod(methodHandle); if (controller.getHandlers().isEmpty()) { controller.remove(); } } } @Override public void postProcessAnnotations(ApplicationMetaModel application) { for (ControllerMetaModel controller : application.getChild(ControllersMetaModel.KEY)) { if (controller.modified) { controller.modified = false; controller.queue(MetaModelEvent.createUpdated(controller)); } } } @Override public void processEvent(ApplicationMetaModel application, MetaModelEvent event) { MetaModelObject obj = event.getObject(); if (obj instanceof ControllerMetaModel) { switch (event.getType()) { case MetaModelEvent.BEFORE_REMOVE: break; case MetaModelEvent.UPDATED: case MetaModelEvent.AFTER_ADD: ControllerMetaModel controller = (ControllerMetaModel)obj; written.add(controller); break; } } } @Override public JSON getDescriptor(ApplicationMetaModel application) { ControllersMetaModel ac = application.getChild(ControllersMetaModel.KEY); // Build routes configuration ArrayList<String> controllers = new ArrayList<String>(); for (ControllerMetaModel controller : ac) { controllers.add(controller.getHandle().getName() + "_"); } // JSON config = new JSON(); config.set("default", ac.defaultController != null ? ac.defaultController.toString() : null); config.set("error", ac.errorController != null ? ac.errorController.toString() : null); config.set("escapeXML", ac.escapeXML); config.map("controllers", controllers); // return config; } /** . */ private static final HashMap<Phase, String> DISPATCH_TYPE = new HashMap<Phase, String>(); static { DISPATCH_TYPE.put(Phase.ACTION, Tools.getName(Phase.Action.Dispatch.class)); DISPATCH_TYPE.put(Phase.VIEW, Tools.getName(Phase.View.Dispatch.class)); DISPATCH_TYPE.put(Phase.RESOURCE, Tools.getName(Phase.Resource.Dispatch.class)); } @Override public void postProcessEvents(ApplicationMetaModel application) { // Validate abstract for (ControllerMetaModel controller : application.getChild(ControllersMetaModel.KEY)) { TypeElement controllerElt = application.getProcessingContext().get(controller.getHandle()); if (controllerElt.getModifiers().contains(Modifier.ABSTRACT)) { throw ControllerMetaModel.CONTROLLER_IS_ABSTRACT.failure(controllerElt, controller.handle.getName()); } } // Check everything is OK here // for (ControllerMetaModel controller : application.getChild(ControllersMetaModel.KEY)) { // for (MethodMetaModel method : controller.getMethods()) { // ExecutableElement executableElt = application.model.processingContext.get(method.handle); // Iterator<? extends VariableElement> i = executableElt.getParameters().iterator(); // for (ParameterMetaModel parameter : method.parameters) { // VariableElement ve = i.next(); // if (parameter instanceof InvocationParameterMetaModel) { // InvocationParameterMetaModel invocationParameter = (InvocationParameterMetaModel)parameter; // TypeElement te = application.model.processingContext.get(invocationParameter.getType()); // if (!te.toString().equals("java.lang.String") && te.getAnnotation(Mapped.class) == null) { // // We should find out who was compiled the bean or the type containing a ref to the class // throw ControllerMetaModel.CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED.failure(ve, ve.getSimpleName()); // } // } // } // } // } // Emit controllers for (Iterator<ControllerMetaModel> i = written.iterator();i.hasNext();) { ControllerMetaModel controller = i.next(); i.remove(); emitController(application.model.processingContext, controller); } } private void emitController(ProcessingContext env, ControllerMetaModel controller) throws ProcessingException { Name fqn = controller.getHandle().getName(); Element origin = env.get(controller.getHandle()); Collection<HandlerMetaModel> methods = controller.getHandlers(); Writer writer = null; try { JavaFileObject file = env.createSourceFile(fqn + "_", origin); writer = file.openWriter(); // writer.append("package ").append(fqn.getParent()).append(";\n"); // Imports writer.append("import ").append(ControllerHandler.class.getCanonicalName()).append(";\n"); writer.append("import ").append(ControlParameter.class.getCanonicalName()).append(";\n"); writer.append("import ").append(PhaseParameter.class.getCanonicalName()).append(";\n"); writer.append("import ").append(ContextualParameter.class.getCanonicalName()).append(";\n"); writer.append("import ").append(BeanParameter.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Tools.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Arrays.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Phase.class.getCanonicalName()).append(";\n"); writer.append("import ").append(ControllerDescriptor.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Generated.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Cardinality.class.getCanonicalName()).append(";\n"); writer.append("import ").append(Request.class.getCanonicalName()).append(";\n"); // Open class writer.append("@Generated(value={})\n"); writer.append("public class ").append(fqn.getIdentifier()).append("_ {\n"); // Class literal writer.append("private static final Class<").append(fqn).append("> TYPE = ").append(fqn).append(".class;\n"); // int index = 0; for (HandlerMetaModel method : methods) { // String methodRef = "method_" + index++; // Method constant writer.append("private static final ").append(METHOD_DESCRIPTOR).append("<"); Tools.nameOf(method.getPhase().getClass(), writer); writer.append("> ").append(methodRef).append(" = "); writer.append("new ").append(METHOD_DESCRIPTOR).append("<"); Tools.nameOf(method.getPhase().getClass(), writer); writer.append(">("); if (method.getId() != null) { writer.append("\"").append(method.getId()).append("\","); } else { writer.append("null,"); } writer.append(PHASE).append(".").append(method.getPhase().name()).append(","); writer.append("TYPE,"); writer.append(TOOLS).append(".safeGetMethod(TYPE,\"").append(method.getName()).append("\""); for (ParameterMetaModel parameter : method.getParameters()) { writer.append(",").append(parameter.type).append(".class"); } writer.append(')'); writer.append(", Arrays.<").append(PARAMETER).append(">asList("); for (int i = 0;i < method.getParameters().size();i++) { ParameterMetaModel parameter = method.getParameters().get(i); if (i > 0) { writer.append(','); } if (parameter instanceof BeanParameterMetaModel) { writer.append("new "). append(BEAN_PARAMETER).append('('). append('"').append(parameter.getName()).append('"').append(','). append(parameter.type).append(".class"). append(')'); } else if (parameter instanceof PhaseParameterMetaModel) { PhaseParameterMetaModel phaseParameter = (PhaseParameterMetaModel)parameter; writer.append("new "). append(PHASE_PARAMETER).append('('). append('"').append(parameter.getName()).append('"').append(','). append(parameter.type).append(".class").append(','). append(phaseParameter.valueType).append(".class").append(','). append(CARDINALITY).append('.').append(phaseParameter.getCardinality().name()).append(','); if (phaseParameter.getAlias() != null) { writer.append('"').append(phaseParameter.getAlias()).append('"'); } else { writer.append("null"); } writer.append(')'); } else { writer.append("new "). append(CONTEXTUAL_PARAMETER).append('('). append('"').append(parameter.getName()).append('"').append(','). append(parameter.type).append(".class"). append(')'); } } writer.append(')'); writer.append(");\n"); // String dispatchType = DISPATCH_TYPE.get(method.getPhase()); // Build list of invocation parameters, i.e phase+bean parameters ArrayList<ParameterMetaModel> parameters = new ArrayList<ParameterMetaModel>(method.getParameters().size()); for (ParameterMetaModel parameter : method.getParameters()) { if (parameter instanceof PhaseParameterMetaModel || parameter instanceof BeanParameterMetaModel) { parameters.add(parameter); } } // We don't generate dispatch for event phase if (method.getPhase() != Phase.EVENT) { // Dispatch literal writer.append("public static ").append(dispatchType).append(" ").append(method.getName()).append("("); for (int i = 0;i < parameters.size();i++) { ParameterMetaModel parameter = parameters.get(i); if (i > 0) { writer.append(','); } writer.append(parameter.type).append(" ").append(parameter.getName()); } writer.append(") { return Request.create").append(method.getPhase().getClass().getSimpleName()).append("Dispatch(").append(methodRef); switch (parameters.size()) { case 0: break; case 1: writer.append(",(Object)").append(parameters.get(0).getName()); break; default: writer.append(",new Object[]{"); for (int j = 0;j < parameters.size();j++) { if (j > 0) { writer.append(","); } writer.append(parameters.get(j).getName()); } writer.append('}'); break; } writer.append("); }\n"); } } // writer.append("public static final ").append(CONTROLLER_DESCRIPTOR).append(" DESCRIPTOR = new ").append(CONTROLLER_DESCRIPTOR).append("("); writer.append("TYPE,Arrays.<").append(METHOD_DESCRIPTOR).append("<?>>asList("); for (int j = 0;j < methods.size();j++) { if (j > 0) { writer.append(','); } writer.append("method_").append(Integer.toString(j)); } writer.append(")"); writer.append(");\n"); // Close class writer.append("}\n"); // env.info("Generated controller companion " + fqn + "_" + " as " + file.toUri()); } catch (IOException e) { throw ControllerMetaModel.CANNOT_WRITE_CONTROLLER_COMPANION.failure(e, origin, controller.getHandle().getName()); } finally { Tools.safeClose(writer); } } }