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