ItEr10S08AdministracionGruposItEr09S09: Created generic mechanism for creating links to controller actions.
This commit is contained in:
parent
38f05bfc80
commit
9b3fa481d7
16 changed files with 464 additions and 10 deletions
|
|
@ -0,0 +1,18 @@
|
|||
package org.navalplanner.web.common;
|
||||
|
||||
/**
|
||||
* Converts from an object to an string representation, and converts the object
|
||||
* back <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public interface Converter<T> {
|
||||
|
||||
Class<T> getType();
|
||||
|
||||
String asString(T entity);
|
||||
|
||||
T asObject(String stringRepresentation);
|
||||
|
||||
String asStringUngeneric(Object entity);
|
||||
|
||||
}
|
||||
|
|
@ -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} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_SINGLETON)
|
||||
public class ConverterFactory implements IConverterFactory {
|
||||
|
||||
private Map<Class<?>, Converter<?>> convertersByType = new HashMap<Class<?>, Converter<?>>();
|
||||
|
||||
@Autowired
|
||||
public ConverterFactory(List<Converter<?>> converters) {
|
||||
for (Converter<?> converter : converters) {
|
||||
convertersByType.put(converter.getType(), converter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Converter<? super T> getConverterFor(Class<T> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_SINGLETON)
|
||||
public class DefaultExecutorRetriever implements ExecutorRetriever {
|
||||
|
||||
@Override
|
||||
public Execution getCurrent() {
|
||||
return Executions.getCurrent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public interface ExecutorRetriever {
|
||||
|
||||
public Execution getCurrent();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.navalplanner.web.common;
|
||||
|
||||
/**
|
||||
* Retrieves a Converter given a type <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public interface IConverterFactory {
|
||||
|
||||
<T> Converter<? super T> getConverterFor(Class<T> klass);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.navalplanner.web.common;
|
||||
|
||||
/**
|
||||
* Contract for {@link RedirectorRegistry} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public interface IRedirectorRegistry {
|
||||
|
||||
public abstract <T> Redirector<T> getRedirectorFor(
|
||||
Class<T> klassWithLinkableMetadata);
|
||||
|
||||
}
|
||||
|
|
@ -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<br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Linkable {
|
||||
public String[] value();
|
||||
}
|
||||
|
|
@ -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 <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Page {
|
||||
|
||||
public String value();
|
||||
|
||||
}
|
||||
|
|
@ -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 <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
|
||||
public @interface Redirecter {
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public class Redirector<T> {
|
||||
|
||||
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<String, LinkableMetadata> metadata = new HashMap<String, LinkableMetadata>();
|
||||
|
||||
private final String page;
|
||||
|
||||
private final IConverterFactory converterFactory;
|
||||
|
||||
public Redirector(IConverterFactory converterFactory,
|
||||
ExecutorRetriever executorRetriever,
|
||||
Class<T> 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 <S extends T> void applyTo(S controller) {
|
||||
Execution current = executorRetriever.getCurrent();
|
||||
Map<String, String> matrixParams = MatrixParameters
|
||||
.extract((HttpServletRequest) current.getNativeRequest());
|
||||
Set<String> matrixParamsNames = matrixParams.keySet();
|
||||
for (Entry<String, LinkableMetadata> entry : metadata.entrySet()) {
|
||||
LinkableMetadata linkableMetadata = entry.getValue();
|
||||
Linkable annotation = linkableMetadata.annotation;
|
||||
HashSet<String> requiredParams = new HashSet<String>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_SINGLETON)
|
||||
public class RedirectorRegistry implements IRedirectorRegistry {
|
||||
|
||||
@Autowired
|
||||
private ExecutorRetriever executorRetriever;
|
||||
|
||||
@Autowired
|
||||
private IConverterFactory converterFactory;
|
||||
|
||||
private Map<Class<?>, Redirector> cached = new HashMap<Class<?>, Redirector>();;
|
||||
|
||||
public <T> Redirector<T> getRedirectorFor(Class<T> klassWithLinkableMetadata) {
|
||||
if (cached.containsKey(klassWithLinkableMetadata))
|
||||
return cached.get(klassWithLinkableMetadata);
|
||||
Redirector<T> result = new Redirector<T>(converterFactory,
|
||||
executorRetriever, klassWithLinkableMetadata);
|
||||
cached.put(klassWithLinkableMetadata, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -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} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_SINGLETON)
|
||||
public class ResourceConverter implements Converter<Resource> {
|
||||
|
||||
@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<Resource> getType() {
|
||||
return Resource.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asStringUngeneric(Object entity) {
|
||||
return asString(getType().cast(entity));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}. <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@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();
|
||||
|
||||
}
|
||||
|
|
@ -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 <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
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<String, String> 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"))));
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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} <br />
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue