blob: 99e5da9e8413c021064b291084f98103ac3055b4 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.pluto.container.bean.processor;
import static org.apache.pluto.container.bean.processor.MethodDescription.METH_ACT;
import static org.apache.pluto.container.bean.processor.MethodDescription.METH_EVT;
import static org.apache.pluto.container.bean.processor.MethodDescription.METH_HDR;
import static org.apache.pluto.container.bean.processor.MethodDescription.METH_REN;
import static org.apache.pluto.container.bean.processor.MethodDescription.METH_RES;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.annotations.ActionMethod;
import javax.portlet.annotations.EventMethod;
import javax.portlet.annotations.HeaderMethod;
import javax.portlet.annotations.PortletSerializable;
import javax.portlet.annotations.PortletSessionScoped;
import javax.portlet.annotations.RenderMethod;
import javax.portlet.annotations.RenderStateScoped;
import javax.portlet.annotations.ServeResourceMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Scott
*
*/
public class PortletAnnotationRecognizer extends AnnotationRecognizer {
private static final Logger LOG = LoggerFactory.getLogger(PortletAnnotationRecognizer.class);
private final static Set<Class<? extends Annotation>> classAnnotations =
new HashSet<Class<? extends Annotation>>();
static {
classAnnotations.add(RenderStateScoped.class);
classAnnotations.add(PortletSessionScoped.class);
classAnnotations.add(SessionScoped.class);
classAnnotations.add(RequestScoped.class);
}
// Maps the annotation class to a list of method descriptions. A given annotation
// may support multiple method signatures, each of which has its own method description.
private final static Map<Class<? extends Annotation>, List<MethodDescription>> descriptions =
new HashMap<Class<? extends Annotation>, List<MethodDescription>>();
static {
ArrayList<MethodDescription> list;
MethodDescription md;
// Add method definitions for action methods
list = new ArrayList<MethodDescription>();
list.add(METH_ACT);
descriptions.put(ActionMethod.class, list);
// Add method definitions for event methods
list = new ArrayList<MethodDescription>();
list.add(METH_EVT);
descriptions.put(EventMethod.class, list);
// Add method definitions for serve resource
list = new ArrayList<MethodDescription>();
list.add(METH_RES);
md = new MethodDescription(String.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.RESOURCE);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.STRING_VOID);
list.add(md);
md = new MethodDescription(void.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.RESOURCE);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.VOID_VOID);
list.add(md);
descriptions.put(ServeResourceMethod.class, list);
// Add method definitions for render
list = new ArrayList<MethodDescription>();
list.add(METH_REN);
md = new MethodDescription(String.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.RENDER);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.STRING_VOID);
list.add(md);
md = new MethodDescription(void.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.RENDER);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.VOID_VOID);
list.add(md);
descriptions.put(RenderMethod.class, list);
// Add method definitions for header methods
list = new ArrayList<MethodDescription>();
list.add(METH_HDR);
md = new MethodDescription(String.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.HEADER);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.STRING_VOID);
list.add(md);
md = new MethodDescription(void.class,
new Class<?>[] {},
new Class<?>[] {PortletException.class, java.io.IOException.class},
MethodType.HEADER);
md.setAllowMultiple(true);
md.setVariant(SignatureVariant.VOID_VOID);
list.add(md);
descriptions.put(HeaderMethod.class, list);
}
private final AnnotatedMethodStore ams;
private final PortletStateScopedConfig stateScopedConfig = new PortletStateScopedConfig();
private final PortletSessionScopedConfig sessionScopedConfig = new PortletSessionScopedConfig();
/**
* @param cla
* @param metha
*/
public PortletAnnotationRecognizer(AnnotatedMethodStore pms, ConfigSummary summary) {
super(classAnnotations, descriptions, summary);
this.ams = pms;
LOG.trace("Created the PortletAnnotationRecognizer.");
}
/**
* Extracts data from the class annotation and stores it for later use.
* <p>
* When reading the PortletConfiguration annotation, data from a later
* annotation will override data from a preceding annotation for a given portlet name.
* <p>
*
*/
@SuppressWarnings("unchecked")
@Override
protected AnnotatedType<?> handleClassAnnotation(Annotation anno, AnnotatedType<?> aType) throws InvalidAnnotationException {
String typeName = aType.getJavaClass().getCanonicalName();
Class<?> theClass = aType.getJavaClass();
if (anno instanceof RenderStateScoped) {
// Verify the render state scoped class, store the annotation &
// bean type with the corresponding bean holder.
if (!PortletSerializable.class.isAssignableFrom(theClass)) {
StringBuilder txt = new StringBuilder(128);
txt.append("Annotation problem: An @RenderStateScoped bean must implement PortletSerializable. ");
txt.append("Annotation: ").append(anno.annotationType().getSimpleName());
txt.append(", Class: ").append(typeName);
LOG.debug(txt.toString());
summary.addStateBeanErrorString(theClass, txt.toString());
} else {
stateScopedConfig.addAnnotation(theClass, (RenderStateScoped) anno);
}
} else if (anno instanceof PortletSessionScoped) {
// Verify the portlet session scoped class, store the annotation &
// bean type with the corresponding bean holder.
PortletSessionScoped pss = (PortletSessionScoped) anno;
if ((pss.value() != PortletSession.APPLICATION_SCOPE) && (pss.value() != PortletSession.PORTLET_SCOPE)) {
StringBuilder txt = new StringBuilder(128);
txt.append("Annotation problem: A @PortletSessionScoped bean scope must be APPLICATION_SCOPE or PORTLET_SCOPE. ");
txt.append("Annotation: ").append(anno.annotationType().getSimpleName());
txt.append(", Scope: ").append(pss.value());
txt.append(", Class: ").append(typeName);
LOG.debug(txt.toString());
summary.addSessionBeanErrorString(theClass, txt.toString());
} else {
sessionScopedConfig.addAnnotation(theClass, pss);
}
} else if (anno instanceof RequestScoped) {
// Wrap the request scoped bean to make it a portlet request scoped bean, add it to
// the summary, and return it so it gets processed by CDI
PortletRequestScopedAnnotatedType at = new PortletRequestScopedAnnotatedType((AnnotatedType<RequestScoped>)aType);
summary.addReqScopedType(at);
return at;
} else if (anno instanceof SessionScoped) {
// Wrap the session scoped bean to make it a portlet session scoped bean with application
// scope, add it to the summary, and return it so it gets processed by CDI
PortletSessionScopedAnnotatedType at = new PortletSessionScopedAnnotatedType((AnnotatedType<SessionScoped>)aType);
PortletSessionScoped pss = at.getAnnotation(PortletSessionScoped.class);
sessionScopedConfig.addAnnotation(theClass, pss);
summary.addAppScopedType(at);
return at;
} else {
// Any other annotation that lands here indicates a programming error
StringBuilder txt = new StringBuilder(128);
txt.append("Unrecognized class annotation: ")
.append(anno.annotationType().getSimpleName());
txt.append(", Class: ").append(typeName);
LOG.warn(txt.toString());
}
return null;
}
/**
* Extracts and stores relevant information from the specified annotation, bean
* class, and Method.
*
* @param anno
* @param beanClass
* @param meth
*/
@Override
protected void handleMethod(Annotation anno, Class<?> beanClass, Method meth, MethodDescription desc) {
ArrayList<String> portletNames = new ArrayList<String>();
String dispatchId = "";
MethodType type = desc.getType();
if (anno instanceof RenderMethod) {
RenderMethod tcc = (RenderMethod) anno;
portletNames.addAll(Arrays.asList(tcc.portletNames()));
dispatchId = tcc.portletMode().toUpperCase();
} else if (anno instanceof HeaderMethod) {
HeaderMethod tcc = (HeaderMethod) anno;
portletNames.addAll(Arrays.asList(tcc.portletNames()));
dispatchId = tcc.portletMode().toUpperCase();
} else if (anno instanceof ActionMethod) {
ActionMethod tcc = (ActionMethod) anno;
portletNames.add(tcc.portletName());
dispatchId = tcc.actionName();
} else if (anno instanceof EventMethod) {
EventMethod tcc = (EventMethod) anno;
portletNames.add(tcc.portletName());
} else if (anno instanceof ServeResourceMethod) {
ServeResourceMethod tcc = (ServeResourceMethod) anno;
portletNames.addAll(Arrays.asList(tcc.portletNames()));
dispatchId = tcc.resourceID();
} else {
StringBuilder txt = new StringBuilder(128);
txt.append("Unrecognized method annotation: ")
.append(anno.annotationType().getSimpleName());
txt.append(", Method: ").append(meth.getName());
txt.append(", Class: ").append(beanClass.getCanonicalName());
LOG.debug(txt.toString());
// Store the error for each portlet name in array
for (String n : getDisplayNames(anno)) {
summary.addErrorString(n, txt.toString());
}
return;
}
// some classAnnotations allow arrays of portlet names. To simplify method retrieval,
// add to the method store with each of the specified portlet names.
// The proxied method is present once, but it is referred to by multiple
// method identifier aliases.
AnnotatedMethod pm = new AnnotatedMethod(anno, beanClass, meth, desc);
for (String portletName : portletNames) {
// The portlet name may not be empty. Note that the annotation itself
// will prevent the field from being null.
if (portletName.length() == 0) {
StringBuilder txt = new StringBuilder(128);
txt.append("Bad portlet name. Name may not be of length 0.");
txt.append(" Annotation: ").append(anno.annotationType().getSimpleName());
txt.append(", Method: ").append(meth.getName());
txt.append(", Class: ").append(beanClass.getCanonicalName());
LOG.debug(txt.toString());
summary.addErrorString(portletName, txt.toString());
return;
}
// Asterisk is ignored if not first and only array element.
if (portletName.equals("*") && portletNames.size() > 1) {
StringBuilder txt = new StringBuilder(128);
txt.append("Wildcard character '*' must be first and only array element. Will be ignored.");
txt.append(" Annotation: ").append(anno.annotationType().getSimpleName());
txt.append(", Method: ").append(meth.getName());
txt.append(", Class: ").append(beanClass.getCanonicalName());
LOG.debug(txt.toString());
summary.addErrorString(portletName, txt.toString());
continue;
}
// we've collected the required info, so store the method.
MethodIdentifier mi = new MethodIdentifier(portletName, dispatchId, type);
ams.addMethod(mi, pm);
}
}
/**
* To be called by the CDI extension afterDeploymentValidation method to
* verify that the stored methods are consistent and to create the bean references.
*
* @param bm BeanManager needed to activate the beans.
* @throws InvalidAnnotationException If the deployment is inconsistent or if the
* beans cannot be instantiated.
*/
@Override
protected void activateCustomScopes(BeanManager bm) {
// Activate the custom scoped beans
stateScopedConfig.activate(bm);
sessionScopedConfig.activate(bm);
}
/**
* To be called by the CDI extension afterDeploymentValidation method to
* verify that the stored methods are consistent.
*/
@Override
protected void activateAnnotatedMethods(BeanManager bm) {
// Verify the portlet names in the proxied method store. It is an error if:
// 1) There is no method for a given name, but configuration data exists
Set<String> names = ams.getPortletNames();
names.remove("*");
for (String name : names) {
StringBuilder txt = new StringBuilder(128);
Set<MethodIdentifier> meths = ams.getMethodIDsForPortlet(name);
if (meths.isEmpty()) {
txt.append("No methods exist for portlet.");
txt.append(" Portlet name: ").append(name);
}
if (txt.length() > 0) {
LOG.debug(txt.toString());
summary.addErrorString(name, txt.toString());
}
}
// See if there is a portlet with a wild-card character. If so, then:
// 1) Create set of names of portlets without errors
// 2) replicate the method identifiers to cover all other portlet names
Set<MethodIdentifier> mis = ams.getMethodIDsForPortlet("*");
if (!mis.isEmpty()) {
names = ams.getPortletNames();
names.removeAll(summary.getPortletsWithErrors());
names.remove("*");
for (MethodIdentifier mi : mis) {
List<AnnotatedMethod> meths = ams.getMethods(mi);
for (String name : names) {
MethodIdentifier newMi = new MethodIdentifier(name, mi.getId(), mi.getType());
for (AnnotatedMethod meth : meths) {
ams.addMethod(newMi, meth);
}
}
}
}
//TODO: remove ams initialization
ams.activateMethods(bm);
}
/**
* @return the stateScopedConfig
*/
public PortletStateScopedConfig getStateScopedConfig() {
return stateScopedConfig;
}
/**
* @return the stateScopedConfig
*/
public PortletSessionScopedConfig getSessionScopedConfig() {
return sessionScopedConfig;
}
}