ItEr10S08AdministracionGruposItEr09S09: Avoided the creation of a dumb controller for doing the redirections, this controller is synthesized instead.

This commit is contained in:
Óscar González Fernández 2009-05-27 18:22:17 +02:00 committed by Javier Moran Rua
parent 9b3fa481d7
commit 6e80beb367
10 changed files with 227 additions and 121 deletions

View file

@ -13,6 +13,6 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Linkable { public @interface LinkToState {
public String[] value(); public String[] value();
} }

View file

@ -11,9 +11,9 @@ import org.springframework.beans.factory.annotation.Qualifier;
* Marks a controller that redirects to the real controller <br /> * Marks a controller that redirects to the real controller <br />
* @author Óscar González Fernández <ogonzalez@igalia.com> * @author Óscar González Fernández <ogonzalez@igalia.com>
*/ */
@Qualifier @Qualifier("linked")
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE }) @Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
public @interface Redirecter { public @interface Linked {
} }

View file

@ -10,8 +10,10 @@ import java.lang.annotation.RetentionPolicy;
*/ */
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Page { public @interface LinksDefiner {
public String value(); public String page();
public String beanName();
} }

View file

@ -11,6 +11,8 @@ import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zkoss.zk.ui.Execution; import org.zkoss.zk.ui.Execution;
/** /**
@ -19,12 +21,14 @@ import org.zkoss.zk.ui.Execution;
*/ */
public class Redirector<T> { public class Redirector<T> {
private static class LinkableMetadata { private static final Log LOG = LogFactory.getLog(Redirector.class);
private static class LinkMetadata {
private final Method method; 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.method = method;
this.annotation = annotation; this.annotation = annotation;
} }
@ -32,57 +36,56 @@ public class Redirector<T> {
private final ExecutorRetriever executorRetriever; private final ExecutorRetriever executorRetriever;
private Map<String, LinkableMetadata> metadata = new HashMap<String, LinkableMetadata>(); private Map<String, LinkMetadata> metadata = new HashMap<String, LinkMetadata>();
private final String page; private final String page;
private final IConverterFactory converterFactory; private final IConverterFactory converterFactory;
public Redirector(IConverterFactory converterFactory, public Redirector(IConverterFactory converterFactory,
ExecutorRetriever executorRetriever, ExecutorRetriever executorRetriever, Class<T> interfaceDefiningLinks) {
Class<T> klassWithLinkableMetadata) { Validate.isTrue(interfaceDefiningLinks.isInterface());
this.converterFactory = converterFactory; this.converterFactory = converterFactory;
this.executorRetriever = executorRetriever; this.executorRetriever = executorRetriever;
Page pageAnnotation = klassWithLinkableMetadata LinksDefiner linkDefiner = interfaceDefiningLinks
.getAnnotation(Page.class); .getAnnotation(LinksDefiner.class);
Validate.notNull(pageAnnotation, Page.class.getName() Validate
+ " annotation required on " .notNull(linkDefiner, LinksDefiner.class.getName()
+ klassWithLinkableMetadata.getName()); + " annotation required on "
this.page = pageAnnotation.value(); + interfaceDefiningLinks.getName());
for (Method method : klassWithLinkableMetadata.getMethods()) { this.page = linkDefiner.page();
Linkable linkable = method.getAnnotation(Linkable.class); for (Method method : interfaceDefiningLinks.getMethods()) {
if (linkable != null) { LinkToState linkToState = method.getAnnotation(LinkToState.class);
metadata.put(method.getName(), new LinkableMetadata(method, if (linkToState != null) {
linkable)); metadata.put(method.getName(), new LinkMetadata(method,
linkToState));
} }
} }
} }
public void doRedirect(Object... values) { public void doRedirect(String methodName, Object... values) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement invoker = stackTrace[2];
String methodName = invoker.getMethodName();
if (!metadata.containsKey(methodName)) { if (!metadata.containsKey(methodName)) {
throw new RuntimeException( LOG.error("Method " + methodName
"It's not invoked on a method in which there is linkable information. Method is: " + "doesn't represent a state(It doesn't have a "
+ methodName); + LinkToState.class.getSimpleName()
+ " annotation). Nothing will be done");
return;
} }
LinkableMetadata linkableMetadata = metadata.get(methodName); LinkMetadata linkableMetadata = metadata.get(methodName);
Class<?>[] types = linkableMetadata.method.getParameterTypes(); Class<?>[] types = linkableMetadata.method.getParameterTypes();
int i = 0;
String[] parameterNames = linkableMetadata.annotation.value(); String[] parameterNames = linkableMetadata.annotation.value();
String[] associatedValues = new String[parameterNames.length]; String[] stringRepresentations = new String[parameterNames.length];
for (Class<?> type : types) { for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
Converter<?> converterFor = converterFactory.getConverterFor(type); Converter<?> converterFor = converterFactory.getConverterFor(type);
associatedValues[i] = converterFor.asStringUngeneric(values[i]); stringRepresentations[i] = converterFor
i++; .asStringUngeneric(values[i]);
} }
StringBuilder linkValue = new StringBuilder(page); StringBuilder linkValue = new StringBuilder(page);
for (int j = 0; j < parameterNames.length; j++) { for (int i = 0; i < parameterNames.length; i++) {
String value = associatedValues[j]; linkValue.append(";").append(parameterNames[i]);
linkValue.append(";").append(parameterNames[j]); if (stringRepresentations[i] != null)
if (value != null) linkValue.append("=").append(stringRepresentations[i]);
linkValue.append("=").append(value);
} }
executorRetriever.getCurrent().sendRedirect(linkValue.toString()); executorRetriever.getCurrent().sendRedirect(linkValue.toString());
} }
@ -104,25 +107,31 @@ public class Redirector<T> {
Map<String, String> matrixParams = MatrixParameters Map<String, String> matrixParams = MatrixParameters
.extract((HttpServletRequest) current.getNativeRequest()); .extract((HttpServletRequest) current.getNativeRequest());
Set<String> matrixParamsNames = matrixParams.keySet(); Set<String> matrixParamsNames = matrixParams.keySet();
for (Entry<String, LinkableMetadata> entry : metadata.entrySet()) { for (Entry<String, LinkMetadata> entry : metadata.entrySet()) {
LinkableMetadata linkableMetadata = entry.getValue(); LinkMetadata linkMetadata = entry.getValue();
Linkable annotation = linkableMetadata.annotation; LinkToState linkToStateAnnotation = linkMetadata.annotation;
HashSet<String> requiredParams = new HashSet<String>(Arrays HashSet<String> requiredParams = new HashSet<String>(Arrays
.asList(annotation.value())); .asList(linkToStateAnnotation.value()));
if (matrixParamsNames.equals(requiredParams)) { if (matrixParamsNames.equals(requiredParams)) {
Class<?>[] parameterTypes = linkableMetadata.method Object[] arguments = retrieveArguments(matrixParams,
.getParameterTypes(); linkToStateAnnotation, linkMetadata.method
Object[] arguments = new Object[parameterTypes.length]; .getParameterTypes());
for (int i = 0; i < parameterTypes.length; i++) { callMethod(controller, linkMetadata.method, arguments);
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; return;
} }
} }
} }
private Object[] retrieveArguments(Map<String, String> 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;
}
} }

View file

@ -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 <br />
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
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<Class<?>> findInterfacesMarkedWithLinkable() {
List<Class<?>> result = new ArrayList<Class<?>>();
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<Class<?>> 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();
}
}

View file

@ -9,7 +9,7 @@ import java.util.Set;
import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.Resource;
import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.business.resources.entities.Worker;
import org.navalplanner.web.common.Util; 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.Component;
import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.EventListener;
@ -34,7 +34,7 @@ public class CriterionWorkersController extends GenericForwardComposer {
private Button cancelListButton; private Button cancelListButton;
private IWorkerCRUDController workerCRUDControllerRedirector; private WorkerCRUDLinks workerCRUD;
public void showList(Event event) { public void showList(Event event) {
loadDataToList(); loadDataToList();
@ -63,7 +63,7 @@ public class CriterionWorkersController extends GenericForwardComposer {
} }
public void goToEditPage(Resource resource) { public void goToEditPage(Resource resource) {
workerCRUDControllerRedirector.goToEditForm((Worker) resource); workerCRUD.goToEditForm((Worker) resource);
} }
@Override @Override

View file

@ -1,9 +1,6 @@
package org.navalplanner.web.resources.worker; package org.navalplanner.web.resources.worker;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.hibernate.validator.InvalidValue; import org.hibernate.validator.InvalidValue;
import org.navalplanner.business.common.exceptions.ValidationException; 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.IMessagesForUser;
import org.navalplanner.web.common.IRedirectorRegistry; import org.navalplanner.web.common.IRedirectorRegistry;
import org.navalplanner.web.common.Level; import org.navalplanner.web.common.Level;
import org.navalplanner.web.common.MatrixParameters;
import org.navalplanner.web.common.MessagesForUser; import org.navalplanner.web.common.MessagesForUser;
import org.navalplanner.web.common.OnlyOneVisible; import org.navalplanner.web.common.OnlyOneVisible;
import org.navalplanner.web.common.Redirector; import org.navalplanner.web.common.Redirector;
@ -25,7 +21,7 @@ import org.zkoss.zul.api.Window;
* @author Óscar González Fernández <ogonzalez@igalia.com> * @author Óscar González Fernández <ogonzalez@igalia.com>
*/ */
public class WorkerCRUDController extends GenericForwardComposer implements public class WorkerCRUDController extends GenericForwardComposer implements
IWorkerCRUDController { WorkerCRUDLinks {
private Window createWindow; private Window createWindow;
@ -128,18 +124,9 @@ public class WorkerCRUDController extends GenericForwardComposer implements
if (messagesContainer == null) if (messagesContainer == null)
throw new RuntimeException("messagesContainer is needed"); throw new RuntimeException("messagesContainer is needed");
messages = new MessagesForUser(messagesContainer); messages = new MessagesForUser(messagesContainer);
Map<String, String> matrixParameters = MatrixParameters Redirector<WorkerCRUDLinks> redirector = redirectorRegistry
.extract((HttpServletRequest) execution.getNativeRequest()); .getRedirectorFor(WorkerCRUDLinks.class);
Redirector redirector = redirectorRegistry
.getRedirectorFor(IWorkerCRUDController.class);
redirector.applyTo(this); redirector.applyTo(this);
// if (matrixParameters.containsKey("create")) {
// goToCreateForm();
// } else if (matrixParameters.containsKey("edit")) {
// goToEditForm(workerModel.findResource(Long
// .parseLong(matrixParameters.get("edit"))));
// }
} }
private LocalizationsController createLocalizationsController( private LocalizationsController createLocalizationsController(

View file

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

View file

@ -1,23 +1,23 @@
package org.navalplanner.web.resources.worker; package org.navalplanner.web.resources.worker;
import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.business.resources.entities.Worker;
import org.navalplanner.web.common.Linkable; import org.navalplanner.web.common.LinksDefiner;
import org.navalplanner.web.common.Page; import org.navalplanner.web.common.LinkToState;
/** /**
* Contract for {@link WorkerCRUDController}. <br /> * Contract for {@link WorkerCRUDController}. <br />
* @author Óscar González Fernández <ogonzalez@igalia.com> * @author Óscar González Fernández <ogonzalez@igalia.com>
*/ */
@Page("/resources/worker/worker.zul") @LinksDefiner(page = "/resources/worker/worker.zul", beanName = "workerCRUD")
public interface IWorkerCRUDController { public interface WorkerCRUDLinks {
@Linkable("edit") @LinkToState("edit")
public abstract void goToEditForm(Worker worker); public abstract void goToEditForm(Worker worker);
@Linkable("workRelationships") @LinkToState("workRelationships")
public abstract void goToWorkRelationshipsForm(Worker worker); public abstract void goToWorkRelationshipsForm(Worker worker);
@Linkable("create") @LinkToState("create")
public abstract void goToCreateForm(); public abstract void goToCreateForm();
} }

View file

@ -13,6 +13,7 @@
required for "@Autowired") required for "@Autowired")
--> -->
<context:annotation-config /> <context:annotation-config />
<bean class="org.navalplanner.web.common.RedirectorSynthetiser"></bean>
<context:component-scan base-package="org.navalplanner.web"/> <context:component-scan base-package="org.navalplanner.web"/>
</beans> </beans>