ItEr10S08AdministracionGruposItEr09S09: Created generic mechanism for creating links to controller actions.

This commit is contained in:
Óscar González Fernández 2009-05-26 17:36:28 +02:00 committed by Javier Moran Rua
parent 38f05bfc80
commit 9b3fa481d7
16 changed files with 464 additions and 10 deletions

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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 {
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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

View file

@ -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();
}

View file

@ -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"))));
// }
}

View file

@ -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);
}
}