diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Converter.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Converter.java
new file mode 100644
index 000000000..9c185a05a
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Converter.java
@@ -0,0 +1,18 @@
+package org.navalplanner.web.common;
+
+/**
+ * Converts from an object to an string representation, and converts the object
+ * back
+ * @author Óscar González Fernández
+ */
+public interface Converter {
+
+ Class getType();
+
+ String asString(T entity);
+
+ T asObject(String stringRepresentation);
+
+ String asStringUngeneric(Object entity);
+
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ConverterFactory.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ConverterFactory.java
new file mode 100644
index 000000000..32237ec32
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ConverterFactory.java
@@ -0,0 +1,42 @@
+package org.navalplanner.web.common;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Default implementation for {@link IConverterFactory}
+ * @author Óscar González Fernández
+ */
+@Component
+@Scope(BeanDefinition.SCOPE_SINGLETON)
+public class ConverterFactory implements IConverterFactory {
+
+ private Map, Converter>> convertersByType = new HashMap, Converter>>();
+
+ @Autowired
+ public ConverterFactory(List> converters) {
+ for (Converter> converter : converters) {
+ convertersByType.put(converter.getType(), converter);
+ }
+ }
+
+ @Override
+ public Converter super T> getConverterFor(Class klass) {
+ if (convertersByType.containsKey(klass))
+ return (Converter super T>) convertersByType.get(klass);
+ for (Class> registeredKlass : convertersByType.keySet()) {
+ if (registeredKlass.isAssignableFrom(klass)) {
+ Converter> result = convertersByType.get(registeredKlass);
+ convertersByType.put(klass, result);
+ return (Converter super T>) result;
+ }
+ }
+ throw new RuntimeException("not found converter for " + klass);
+ }
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/DefaultExecutorRetriever.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/DefaultExecutorRetriever.java
new file mode 100644
index 000000000..88621fe59
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/DefaultExecutorRetriever.java
@@ -0,0 +1,22 @@
+package org.navalplanner.web.common;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.zkoss.zk.ui.Execution;
+import org.zkoss.zk.ui.Executions;
+
+/**
+ * Uses {@link Executions#getCurrent()}
+ * @author Óscar González Fernández
+ */
+@Component
+@Scope(BeanDefinition.SCOPE_SINGLETON)
+public class DefaultExecutorRetriever implements ExecutorRetriever {
+
+ @Override
+ public Execution getCurrent() {
+ return Executions.getCurrent();
+ }
+
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ExecutorRetriever.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ExecutorRetriever.java
new file mode 100644
index 000000000..bffefc923
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ExecutorRetriever.java
@@ -0,0 +1,13 @@
+package org.navalplanner.web.common;
+
+import org.zkoss.zk.ui.Execution;
+
+/**
+ * It's used for retrieving the current {@link Execution} object
+ * @author Óscar González Fernández
+ */
+public interface ExecutorRetriever {
+
+ public Execution getCurrent();
+
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IConverterFactory.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IConverterFactory.java
new file mode 100644
index 000000000..34a07fcdc
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IConverterFactory.java
@@ -0,0 +1,11 @@
+package org.navalplanner.web.common;
+
+/**
+ * Retrieves a Converter given a type
+ * @author Óscar González Fernández
+ */
+public interface IConverterFactory {
+
+ Converter super T> getConverterFor(Class klass);
+
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IRedirectorRegistry.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IRedirectorRegistry.java
new file mode 100644
index 000000000..38155d04b
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/IRedirectorRegistry.java
@@ -0,0 +1,12 @@
+package org.navalplanner.web.common;
+
+/**
+ * Contract for {@link RedirectorRegistry}
+ * @author Óscar González Fernández
+ */
+public interface IRedirectorRegistry {
+
+ public abstract Redirector getRedirectorFor(
+ Class klassWithLinkableMetadata);
+
+}
\ No newline at end of file
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java
new file mode 100644
index 000000000..1cc49ae64
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Linkable.java
@@ -0,0 +1,18 @@
+package org.navalplanner.web.common;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method that can be linked to using matrix parameters
+ * @author Óscar González Fernández
+ */
+@Target(ElementType.METHOD)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Linkable {
+ public String[] value();
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java
new file mode 100644
index 000000000..b7816ef4a
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Page.java
@@ -0,0 +1,17 @@
+package org.navalplanner.web.common;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Tells which is the base url
+ * @author Óscar González Fernández
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Page {
+
+ 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/Redirecter.java
new file mode 100644
index 000000000..95d4ee986
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirecter.java
@@ -0,0 +1,19 @@
+package org.navalplanner.web.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+
+/**
+ * Marks a controller that redirects to the real controller
+ * @author Óscar González Fernández
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
+public @interface Redirecter {
+
+}
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
new file mode 100644
index 000000000..b3d3b0789
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Redirector.java
@@ -0,0 +1,128 @@
+package org.navalplanner.web.common;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang.Validate;
+import org.zkoss.zk.ui.Execution;
+
+/**
+ *
+ * @author Óscar González Fernández
+ */
+public class Redirector {
+
+ private static class LinkableMetadata {
+ private final Method method;
+
+ private final Linkable annotation;
+
+ private LinkableMetadata(Method method, Linkable annotation) {
+ this.method = method;
+ this.annotation = annotation;
+ }
+ }
+
+ private final ExecutorRetriever executorRetriever;
+
+ private Map metadata = new HashMap();
+
+ private final String page;
+
+ private final IConverterFactory converterFactory;
+
+ public Redirector(IConverterFactory converterFactory,
+ ExecutorRetriever executorRetriever,
+ Class klassWithLinkableMetadata) {
+ 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));
+ }
+ }
+ }
+
+ public void doRedirect(Object... values) {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ StackTraceElement invoker = stackTrace[2];
+ String methodName = invoker.getMethodName();
+ if (!metadata.containsKey(methodName)) {
+ throw new RuntimeException(
+ "It's not invoked on a method in which there is linkable information. Method is: "
+ + methodName);
+ }
+ LinkableMetadata 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) {
+ Converter> converterFor = converterFactory.getConverterFor(type);
+ associatedValues[i] = converterFor.asStringUngeneric(values[i]);
+ 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);
+ }
+ executorRetriever.getCurrent().sendRedirect(linkValue.toString());
+ }
+
+ private static void callMethod(Object target, Method superclassMethod,
+ Object[] params) {
+ try {
+ Method method = target.getClass().getMethod(
+ superclassMethod.getName(),
+ superclassMethod.getParameterTypes());
+ method.invoke(target, params);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void applyTo(S controller) {
+ Execution current = executorRetriever.getCurrent();
+ Map matrixParams = MatrixParameters
+ .extract((HttpServletRequest) current.getNativeRequest());
+ Set matrixParamsNames = matrixParams.keySet();
+ for (Entry entry : metadata.entrySet()) {
+ LinkableMetadata linkableMetadata = entry.getValue();
+ Linkable annotation = linkableMetadata.annotation;
+ HashSet requiredParams = new HashSet(Arrays
+ .asList(annotation.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);
+ return;
+ }
+ }
+ }
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorRegistry.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorRegistry.java
new file mode 100644
index 000000000..2d8527cd8
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/RedirectorRegistry.java
@@ -0,0 +1,35 @@
+package org.navalplanner.web.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * Registry of {@link Redirector}
+ * @author Óscar González Fernández
+ */
+@Component
+@Scope(BeanDefinition.SCOPE_SINGLETON)
+public class RedirectorRegistry implements IRedirectorRegistry {
+
+ @Autowired
+ private ExecutorRetriever executorRetriever;
+
+ @Autowired
+ private IConverterFactory converterFactory;
+
+ private Map, Redirector> cached = new HashMap, Redirector>();;
+
+ public Redirector getRedirectorFor(Class klassWithLinkableMetadata) {
+ if (cached.containsKey(klassWithLinkableMetadata))
+ return cached.get(klassWithLinkableMetadata);
+ Redirector result = new Redirector(converterFactory,
+ executorRetriever, klassWithLinkableMetadata);
+ cached.put(klassWithLinkableMetadata, result);
+ return result;
+ }
+}
diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ResourceConverter.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ResourceConverter.java
new file mode 100644
index 000000000..0f9369259
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/ResourceConverter.java
@@ -0,0 +1,47 @@
+package org.navalplanner.web.common;
+
+import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
+import org.navalplanner.business.resources.entities.Resource;
+import org.navalplanner.business.resources.services.ResourceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * A {@link Converter} for {@link Resource}
+ * @author Óscar González Fernández
+ */
+@Component
+@Scope(BeanDefinition.SCOPE_SINGLETON)
+public class ResourceConverter implements Converter {
+
+ @Autowired
+ private ResourceService resourceService;
+
+ @Override
+ public Resource asObject(String stringRepresentation) {
+ long id = Long.parseLong(stringRepresentation);
+ try {
+ return resourceService.findResource(id);
+ } catch (InstanceNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String asString(Resource entity) {
+ return entity.getId() + "";
+ }
+
+ @Override
+ public Class getType() {
+ return Resource.class;
+ }
+
+ @Override
+ public String asStringUngeneric(Object entity) {
+ return asString(getType().cast(entity));
+ }
+
+}
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 c411a391f..201ce31d2 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,6 +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.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
@@ -33,6 +34,8 @@ public class CriterionWorkersController extends GenericForwardComposer {
private Button cancelListButton;
+ private IWorkerCRUDController workerCRUDControllerRedirector;
+
public void showList(Event event) {
loadDataToList();
try {
@@ -60,9 +63,7 @@ public class CriterionWorkersController extends GenericForwardComposer {
}
public void goToEditPage(Resource resource) {
- System.out.println("going to edit page for: " + resource);
- desktop.getExecution().sendRedirect(
- "/resources/worker/worker.zul;edit=" + resource.getId());
+ workerCRUDControllerRedirector.goToEditForm((Worker) resource);
}
@Override
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/IWorkerCRUDController.java
new file mode 100644
index 000000000..6b3bdecec
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerCRUDController.java
@@ -0,0 +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;
+
+/**
+ * Contract for {@link WorkerCRUDController}.
+ * @author Óscar González Fernández
+ */
+@Page("/resources/worker/worker.zul")
+public interface IWorkerCRUDController {
+
+ @Linkable("edit")
+ public abstract void goToEditForm(Worker worker);
+
+ @Linkable("workRelationships")
+ public abstract void goToWorkRelationshipsForm(Worker worker);
+
+ @Linkable("create")
+ public abstract void goToCreateForm();
+
+}
\ No newline at end of file
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 66a8dd759..13ce9f4a6 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
@@ -9,10 +9,12 @@ import org.hibernate.validator.InvalidValue;
import org.navalplanner.business.common.exceptions.ValidationException;
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;
import org.navalplanner.web.common.Util;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.util.GenericForwardComposer;
@@ -22,7 +24,8 @@ import org.zkoss.zul.api.Window;
* Controller for {@link Worker} resource
* @author Óscar González Fernández
*/
-public class WorkerCRUDController extends GenericForwardComposer {
+public class WorkerCRUDController extends GenericForwardComposer implements
+ IWorkerCRUDController {
private Window createWindow;
@@ -34,6 +37,8 @@ public class WorkerCRUDController extends GenericForwardComposer {
private IWorkerModel workerModel;
+ private IRedirectorRegistry redirectorRegistry;
+
private OnlyOneVisible visibility;
private IMessagesForUser messages;
@@ -125,12 +130,15 @@ public class WorkerCRUDController extends GenericForwardComposer {
messages = new MessagesForUser(messagesContainer);
Map matrixParameters = MatrixParameters
.extract((HttpServletRequest) execution.getNativeRequest());
- if (matrixParameters.containsKey("create")) {
- goToCreateForm();
- } else if (matrixParameters.containsKey("edit")) {
- goToEditForm(workerModel.findResource(Long
- .parseLong(matrixParameters.get("edit"))));
- }
+ Redirector redirector = redirectorRegistry
+ .getRedirectorFor(IWorkerCRUDController.class);
+ redirector.applyTo(this);
+ // if (matrixParameters.containsKey("create")) {
+ // goToCreateForm();
+ // } else if (matrixParameters.containsKey("edit")) {
+ // goToEditForm(workerModel.findResource(Long
+ // .parseLong(matrixParameters.get("edit"))));
+ // }
}
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
new file mode 100644
index 000000000..be717dd06
--- /dev/null
+++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDControllerRedirector.java
@@ -0,0 +1,40 @@
+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);
+ }
+
+}