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 getConverterFor(Class klass) { + if (convertersByType.containsKey(klass)) + return (Converter) convertersByType.get(klass); + for (Class registeredKlass : convertersByType.keySet()) { + if (registeredKlass.isAssignableFrom(klass)) { + Converter result = convertersByType.get(registeredKlass); + convertersByType.put(klass, result); + return (Converter) 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 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); + } + +}