diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinkToState.java similarity index 93% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinkToState.java index 1cc49ae64..0db2a38e0 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinkToState.java @@ -13,6 +13,6 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) -public @interface Linkable { +public @interface LinkToState { public String[] value(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirecter.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linked.java similarity index 91% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirecter.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linked.java index 95d4ee986..8436bf956 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirecter.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linked.java @@ -11,9 +11,9 @@ import org.springframework.beans.factory.annotation.Qualifier; * Marks a controller that redirects to the real controller
* @author Óscar González Fernández */ -@Qualifier +@Qualifier("linked") @Retention(RetentionPolicy.RUNTIME) @Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE }) -public @interface Redirecter { +public @interface Linked { } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinksDefiner.java similarity index 78% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinksDefiner.java index b7816ef4a..7605d3b5b 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/LinksDefiner.java @@ -10,8 +10,10 @@ import java.lang.annotation.RetentionPolicy; */ @Documented @Retention(RetentionPolicy.RUNTIME) -public @interface Page { +public @interface LinksDefiner { - public String value(); + public String page(); + + public String beanName(); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirector.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirector.java index b3d3b0789..220b199fb 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirector.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirector.java @@ -11,6 +11,8 @@ import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.Validate; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.zkoss.zk.ui.Execution; /** @@ -19,12 +21,14 @@ import org.zkoss.zk.ui.Execution; */ public class Redirector { - private static class LinkableMetadata { + private static final Log LOG = LogFactory.getLog(Redirector.class); + + private static class LinkMetadata { private final Method method; - private final Linkable annotation; + private final LinkToState annotation; - private LinkableMetadata(Method method, Linkable annotation) { + private LinkMetadata(Method method, LinkToState annotation) { this.method = method; this.annotation = annotation; } @@ -32,57 +36,56 @@ public class Redirector { private final ExecutorRetriever executorRetriever; - private Map metadata = new HashMap(); + private Map metadata = new HashMap(); private final String page; private final IConverterFactory converterFactory; public Redirector(IConverterFactory converterFactory, - ExecutorRetriever executorRetriever, - Class klassWithLinkableMetadata) { + ExecutorRetriever executorRetriever, Class interfaceDefiningLinks) { + Validate.isTrue(interfaceDefiningLinks.isInterface()); this.converterFactory = converterFactory; this.executorRetriever = executorRetriever; - Page pageAnnotation = klassWithLinkableMetadata - .getAnnotation(Page.class); - Validate.notNull(pageAnnotation, Page.class.getName() - + " annotation required on " - + klassWithLinkableMetadata.getName()); - this.page = pageAnnotation.value(); - for (Method method : klassWithLinkableMetadata.getMethods()) { - Linkable linkable = method.getAnnotation(Linkable.class); - if (linkable != null) { - metadata.put(method.getName(), new LinkableMetadata(method, - linkable)); + LinksDefiner linkDefiner = interfaceDefiningLinks + .getAnnotation(LinksDefiner.class); + Validate + .notNull(linkDefiner, LinksDefiner.class.getName() + + " annotation required on " + + interfaceDefiningLinks.getName()); + this.page = linkDefiner.page(); + for (Method method : interfaceDefiningLinks.getMethods()) { + LinkToState linkToState = method.getAnnotation(LinkToState.class); + if (linkToState != null) { + metadata.put(method.getName(), new LinkMetadata(method, + linkToState)); } } } - public void doRedirect(Object... values) { - StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - StackTraceElement invoker = stackTrace[2]; - String methodName = invoker.getMethodName(); + public void doRedirect(String methodName, Object... values) { if (!metadata.containsKey(methodName)) { - throw new RuntimeException( - "It's not invoked on a method in which there is linkable information. Method is: " - + methodName); + LOG.error("Method " + methodName + + "doesn't represent a state(It doesn't have a " + + LinkToState.class.getSimpleName() + + " annotation). Nothing will be done"); + return; } - LinkableMetadata linkableMetadata = metadata.get(methodName); + LinkMetadata linkableMetadata = metadata.get(methodName); Class[] types = linkableMetadata.method.getParameterTypes(); - int i = 0; String[] parameterNames = linkableMetadata.annotation.value(); - String[] associatedValues = new String[parameterNames.length]; - for (Class type : types) { + String[] stringRepresentations = new String[parameterNames.length]; + for (int i = 0; i < types.length; i++) { + Class type = types[i]; Converter converterFor = converterFactory.getConverterFor(type); - associatedValues[i] = converterFor.asStringUngeneric(values[i]); - i++; + stringRepresentations[i] = converterFor + .asStringUngeneric(values[i]); } StringBuilder linkValue = new StringBuilder(page); - for (int j = 0; j < parameterNames.length; j++) { - String value = associatedValues[j]; - linkValue.append(";").append(parameterNames[j]); - if (value != null) - linkValue.append("=").append(value); + for (int i = 0; i < parameterNames.length; i++) { + linkValue.append(";").append(parameterNames[i]); + if (stringRepresentations[i] != null) + linkValue.append("=").append(stringRepresentations[i]); } executorRetriever.getCurrent().sendRedirect(linkValue.toString()); } @@ -104,25 +107,31 @@ public class Redirector { Map matrixParams = MatrixParameters .extract((HttpServletRequest) current.getNativeRequest()); Set matrixParamsNames = matrixParams.keySet(); - for (Entry entry : metadata.entrySet()) { - LinkableMetadata linkableMetadata = entry.getValue(); - Linkable annotation = linkableMetadata.annotation; + for (Entry entry : metadata.entrySet()) { + LinkMetadata linkMetadata = entry.getValue(); + LinkToState linkToStateAnnotation = linkMetadata.annotation; HashSet requiredParams = new HashSet(Arrays - .asList(annotation.value())); + .asList(linkToStateAnnotation.value())); if (matrixParamsNames.equals(requiredParams)) { - Class[] parameterTypes = linkableMetadata.method - .getParameterTypes(); - Object[] arguments = new Object[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - Object argumentName = annotation.value()[i]; - String parameterValue = matrixParams.get(argumentName); - Converter converter = converterFactory - .getConverterFor(parameterTypes[i]); - arguments[i] = converter.asObject(parameterValue); - } - callMethod(controller, linkableMetadata.method, arguments); + Object[] arguments = retrieveArguments(matrixParams, + linkToStateAnnotation, linkMetadata.method + .getParameterTypes()); + callMethod(controller, linkMetadata.method, arguments); return; } } } + + private Object[] retrieveArguments(Map matrixParams, + LinkToState linkToStateAnnotation, Class[] parameterTypes) { + Object[] result = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + Object argumentName = linkToStateAnnotation.value()[i]; + String parameterValue = matrixParams.get(argumentName); + Converter converter = converterFactory + .getConverterFor(parameterTypes[i]); + result[i] = converter.asObject(parameterValue); + } + return result; + } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorSynthetiser.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorSynthetiser.java new file mode 100644 index 000000000..3394c40b5 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorSynthetiser.java @@ -0,0 +1,147 @@ +package org.navalplanner.web.common; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.util.ClassUtils; + +/** + * Creates implemnetations of controllers that sends http redirects to the + * proper page
+ * @author Óscar González Fernández + */ +public class RedirectorSynthetiser implements BeanFactoryPostProcessor { + private static final Log LOG = LogFactory + .getLog(RedirectorSynthetiser.class); + + private final class SynthetizedImplementation implements InvocationHandler { + private final ConfigurableListableBeanFactory beanFactory; + + private final Class pageInterface; + + private Redirector redirector; + + private SynthetizedImplementation( + ConfigurableListableBeanFactory beanFactory, + Class pageInterface) { + this.beanFactory = beanFactory; + this.pageInterface = pageInterface; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + Redirector redirector = getRedirector(); + redirector.doRedirect(method.getName(), args); + return null; + } + + private Redirector getRedirector() { + if (redirector != null) + return redirector; + RedirectorRegistry redirectorRegistry = (RedirectorRegistry) beanFactory + .getBean(getSpringDefaultName(RedirectorRegistry.class), + RedirectorRegistry.class); + redirector = redirectorRegistry.getRedirectorFor(pageInterface); + return redirector; + } + } + + public void postProcessBeanFactory( + ConfigurableListableBeanFactory beanFactory) throws BeansException { + long elapsedTime = System.currentTimeMillis(); + for (Class pageInterface : findInterfacesMarkedWithLinkable()) { + beanFactory.registerSingleton(getBeanName(pageInterface), + createRedirectorImplementationFor(beanFactory, + pageInterface)); + } + elapsedTime = System.currentTimeMillis() - elapsedTime; + LOG.debug("Took " + elapsedTime + + " ms to search for interfaces annotated with " + + LinksDefiner.class.getSimpleName()); + } + + private List> findInterfacesMarkedWithLinkable() { + List> result = new ArrayList>(); + PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory( + resourceResolver); + for (Resource resource : findResourcesCouldMatch(resourceResolver)) { + addIfSuitable(result, metadataReaderFactory, resource); + } + return result; + } + + private Resource[] findResourcesCouldMatch( + PathMatchingResourcePatternResolver resourceResolver) { + try { + return resourceResolver + .getResources("classpath*:" + + ClassUtils + .convertClassNameToResourcePath("org.navalplanner.web") + + "/" + "**/*.class"); + } catch (IOException e) { + throw new RuntimeException("couldn't load any resource", e); + } + } + + private void addIfSuitable(List> accumulatedResult, + CachingMetadataReaderFactory metadataReaderFactory, + Resource resource) { + try { + if (resource.isReadable()) { + MetadataReader metadataReader = metadataReaderFactory + .getMetadataReader(resource); + AnnotationMetadata annotationMetadata = metadataReader + .getAnnotationMetadata(); + ClassMetadata classMetadata = metadataReader.getClassMetadata(); + if (classMetadata.isInterface() + && annotationMetadata.getAnnotationTypes().contains( + LinksDefiner.class.getName())) { + Class klass = Class + .forName(classMetadata.getClassName()); + if (klass.isInterface()) { + accumulatedResult.add(klass); + } + } + } + } catch (Exception e) { + LOG.warn("exception processing " + resource, e); + } + } + + private Object createRedirectorImplementationFor( + final ConfigurableListableBeanFactory beanFactory, + final Class pageInterface) { + + return Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { pageInterface }, new SynthetizedImplementation( + beanFactory, pageInterface)); + } + + private static String getSpringDefaultName(Class klass) { + String simpleName = klass.getSimpleName(); + return simpleName.substring(0, 1).toLowerCase() + + simpleName.substring(1); + } + + private static String getBeanName(Class pageInterface) { + LinksDefiner annotation = pageInterface.getAnnotation(LinksDefiner.class); + return annotation.beanName(); + } +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/criterion/CriterionWorkersController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/criterion/CriterionWorkersController.java index 201ce31d2..a2610dba4 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/criterion/CriterionWorkersController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/criterion/CriterionWorkersController.java @@ -9,7 +9,7 @@ import java.util.Set; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.web.common.Util; -import org.navalplanner.web.resources.worker.IWorkerCRUDController; +import org.navalplanner.web.resources.worker.WorkerCRUDLinks; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; @@ -34,7 +34,7 @@ public class CriterionWorkersController extends GenericForwardComposer { private Button cancelListButton; - private IWorkerCRUDController workerCRUDControllerRedirector; + private WorkerCRUDLinks workerCRUD; public void showList(Event event) { loadDataToList(); @@ -63,7 +63,7 @@ public class CriterionWorkersController extends GenericForwardComposer { } public void goToEditPage(Resource resource) { - workerCRUDControllerRedirector.goToEditForm((Worker) resource); + workerCRUD.goToEditForm((Worker) resource); } @Override diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java index 13ce9f4a6..c04aeb9b3 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java @@ -1,9 +1,6 @@ package org.navalplanner.web.resources.worker; import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; import org.hibernate.validator.InvalidValue; import org.navalplanner.business.common.exceptions.ValidationException; @@ -11,7 +8,6 @@ import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.web.common.IMessagesForUser; import org.navalplanner.web.common.IRedirectorRegistry; import org.navalplanner.web.common.Level; -import org.navalplanner.web.common.MatrixParameters; import org.navalplanner.web.common.MessagesForUser; import org.navalplanner.web.common.OnlyOneVisible; import org.navalplanner.web.common.Redirector; @@ -25,7 +21,7 @@ import org.zkoss.zul.api.Window; * @author Óscar González Fernández */ public class WorkerCRUDController extends GenericForwardComposer implements - IWorkerCRUDController { + WorkerCRUDLinks { private Window createWindow; @@ -128,18 +124,9 @@ public class WorkerCRUDController extends GenericForwardComposer implements if (messagesContainer == null) throw new RuntimeException("messagesContainer is needed"); messages = new MessagesForUser(messagesContainer); - Map matrixParameters = MatrixParameters - .extract((HttpServletRequest) execution.getNativeRequest()); - Redirector redirector = redirectorRegistry - .getRedirectorFor(IWorkerCRUDController.class); + Redirector redirector = redirectorRegistry + .getRedirectorFor(WorkerCRUDLinks.class); redirector.applyTo(this); - // if (matrixParameters.containsKey("create")) { - // goToCreateForm(); - // } else if (matrixParameters.containsKey("edit")) { - // goToEditForm(workerModel.findResource(Long - // .parseLong(matrixParameters.get("edit")))); - // } - } private LocalizationsController createLocalizationsController( diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDControllerRedirector.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDControllerRedirector.java deleted file mode 100644 index be717dd06..000000000 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDControllerRedirector.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.navalplanner.web.resources.worker; - -import org.navalplanner.business.resources.entities.Worker; -import org.navalplanner.web.common.IRedirectorRegistry; -import org.navalplanner.web.common.Redirecter; -import org.navalplanner.web.common.Redirector; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Redirects to the page composed by {@link WorkerCRUDController}
- * @author Óscar González Fernández - */ -@Component -@Redirecter -public class WorkerCRUDControllerRedirector implements IWorkerCRUDController { - - private Redirector redirector; - - @Autowired - public WorkerCRUDControllerRedirector(IRedirectorRegistry registry) { - redirector = registry.getRedirectorFor(IWorkerCRUDController.class); - } - - @Override - public void goToCreateForm() { - redirector.doRedirect(); - } - - @Override - public void goToEditForm(Worker worker) { - redirector.doRedirect(worker); - } - - @Override - public void goToWorkRelationshipsForm(Worker worker) { - redirector.doRedirect(worker); - } - -} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDLinks.java similarity index 57% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerCRUDController.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDLinks.java index 6b3bdecec..1491e3e20 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerCRUDController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDLinks.java @@ -1,23 +1,23 @@ package org.navalplanner.web.resources.worker; import org.navalplanner.business.resources.entities.Worker; -import org.navalplanner.web.common.Linkable; -import org.navalplanner.web.common.Page; +import org.navalplanner.web.common.LinksDefiner; +import org.navalplanner.web.common.LinkToState; /** * Contract for {@link WorkerCRUDController}.
* @author Óscar González Fernández */ -@Page("/resources/worker/worker.zul") -public interface IWorkerCRUDController { +@LinksDefiner(page = "/resources/worker/worker.zul", beanName = "workerCRUD") +public interface WorkerCRUDLinks { - @Linkable("edit") + @LinkToState("edit") public abstract void goToEditForm(Worker worker); - @Linkable("workRelationships") + @LinkToState("workRelationships") public abstract void goToWorkRelationshipsForm(Worker worker); - @Linkable("create") + @LinkToState("create") public abstract void goToCreateForm(); } \ No newline at end of file diff --git a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml index 025f1c450..f65f8c252 100644 --- a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml +++ b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-config.xml @@ -13,6 +13,7 @@ required for "@Autowired") --> + \ No newline at end of file