001: /*
002: * Copyright 2006 Maik Schreiber <blizzy AT blizzy DOT de>
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.annotations;
017:
018: import java.beans.Introspector;
019: import java.lang.reflect.Field;
020: import java.lang.reflect.Method;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.commons.logging.Log;
029: import org.directwebremoting.AjaxFilter;
030: import org.directwebremoting.Container;
031: import org.directwebremoting.convert.BeanConverter;
032: import org.directwebremoting.create.NewCreator;
033: import org.directwebremoting.extend.AccessControl;
034: import org.directwebremoting.extend.AjaxFilterManager;
035: import org.directwebremoting.extend.Configurator;
036: import org.directwebremoting.extend.Converter;
037: import org.directwebremoting.extend.ConverterManager;
038: import org.directwebremoting.extend.Creator;
039: import org.directwebremoting.extend.CreatorManager;
040: import org.directwebremoting.util.LocalUtil;
041:
042: /**
043: * A Configurator that works off Annotations.
044: * @author Maik Schreiber [blizzy AT blizzy DOT de]
045: * @author Joe Walker [joe at getahead dot ltd dot uk]
046: */
047: public class AnnotationsConfigurator implements Configurator {
048: /* (non-Javadoc)
049: * @see org.directwebremoting.Configurator#configure(org.directwebremoting.Container)
050: */
051: public void configure(Container container) {
052: for (Class<?> clazz : getClasses(container)) {
053: try {
054: processClass(clazz, container);
055: } catch (Exception ex) {
056: log.error(
057: "Failed to process class: " + clazz.getName(),
058: ex);
059: }
060: }
061: }
062:
063: /**
064: * Allow subclasses to override the default way we find out which classes
065: * have DWR annotations for us to work with
066: * @param container Commonly we get configuration information from here
067: * @return A set of classes with DWR annotations
068: */
069: protected Set<Class<?>> getClasses(Container container) {
070: Set<Class<?>> classes = new HashSet<Class<?>>();
071:
072: Object data = container.getBean("classes");
073: if (data != null) {
074: if (data instanceof String) {
075: String classesStr = (String) data;
076: for (String element : classesStr.split(",")) {
077: try {
078: Class<?> clazz = LocalUtil.classForName(element
079: .trim());
080: classes.add(clazz);
081: } catch (Exception ex) {
082: log.error(
083: "Failed to process class: " + element,
084: ex);
085: }
086: }
087: } else {
088: try {
089: classes.add(data.getClass());
090: } catch (Exception ex) {
091: log.error("Failed to process class: "
092: + data.getClass().getName(), ex);
093: }
094: }
095: }
096:
097: return classes;
098: }
099:
100: /**
101: * Process the annotations on a given class
102: * @param clazz The class to search for annotations
103: * @param container The IoC container to configure
104: * @throws IllegalAccessException If annotation processing fails
105: * @throws InstantiationException If annotation processing fails
106: */
107: protected void processClass(Class<?> clazz, Container container)
108: throws InstantiationException, IllegalAccessException {
109: RemoteProxy createAnn = clazz.getAnnotation(RemoteProxy.class);
110: if (createAnn != null) {
111: processCreate(clazz, createAnn, container);
112: }
113:
114: DataTransferObject convertAnn = clazz
115: .getAnnotation(DataTransferObject.class);
116: if (convertAnn != null) {
117: processConvert(clazz, convertAnn, container);
118: }
119:
120: GlobalFilter globalFilterAnn = clazz
121: .getAnnotation(GlobalFilter.class);
122: if (globalFilterAnn != null) {
123: processGlobalFilter(clazz, globalFilterAnn, container);
124: }
125: }
126:
127: /**
128: * Process the @RemoteProxy annotation on a given class
129: * @param clazz The class annotated with @RemoteProxy
130: * @param createAnn The annotation
131: * @param container The IoC container to configure
132: */
133: protected void processCreate(Class<?> clazz, RemoteProxy createAnn,
134: Container container) {
135: Class<? extends Creator> creator = createAnn.creator();
136: String creatorClass = creator.getName();
137: Map<String, String> creatorParams = getParamsMap(createAnn
138: .creatorParams());
139: ScriptScope scope = createAnn.scope();
140:
141: CreatorManager creatorManager = container
142: .getBean(CreatorManager.class);
143: String creatorName = creatorClass.replace(".", "_");
144: creatorManager.addCreatorType(creatorName, creatorClass);
145:
146: Map<String, String> params = new HashMap<String, String>();
147: if (NewCreator.class.isAssignableFrom(creator)) {
148: params.put("class", clazz.getName());
149: }
150: params.putAll(creatorParams);
151: params.put("scope", scope.getValue());
152:
153: String name = createAnn.name();
154: if (name == null || name.length() == 0) {
155: name = clazz.getSimpleName();
156: }
157:
158: try {
159: log.info("Adding class " + clazz.getName() + " as " + name);
160: creatorManager.addCreator(name, creatorName, params);
161: } catch (Exception ex) {
162: log.error("Failed to add class as Creator: "
163: + clazz.getName(), ex);
164: }
165:
166: AccessControl accessControl = container
167: .getBean(AccessControl.class);
168: for (Method method : clazz.getMethods()) {
169: if (method.getAnnotation(RemoteMethod.class) != null) {
170: accessControl.addIncludeRule(name, method.getName());
171:
172: Auth authAnn = method.getAnnotation(Auth.class);
173: if (authAnn != null) {
174: for (String role : authAnn.role()) {
175: accessControl.addRoleRestriction(name, method
176: .getName(), role);
177: }
178: }
179: }
180: }
181:
182: Filters filtersAnn = clazz.getAnnotation(Filters.class);
183: if (filtersAnn != null) {
184: Filter[] fs = filtersAnn.value();
185: for (Filter filter : fs) {
186: processFilter(filter, name, container);
187: }
188: }
189: // process single filter for convenience
190: else {
191: Filter filterAnn = clazz.getAnnotation(Filter.class);
192: if (filterAnn != null) {
193: processFilter(filterAnn, name, container);
194: }
195: }
196: }
197:
198: /**
199: * Process the @Filter annotaion
200: * @param filterAnn The filter annotation
201: * @param name The Javascript name of the class to filter
202: * @param container The IoC container to configure
203: */
204: protected void processFilter(Filter filterAnn, String name,
205: Container container) {
206: Map<String, String> filterParams = getParamsMap(filterAnn
207: .params());
208: AjaxFilter filter = LocalUtil.classNewInstance(name, filterAnn
209: .type().getName(), AjaxFilter.class);
210: if (filter != null) {
211: LocalUtil.setParams(filter, filterParams, null);
212: AjaxFilterManager filterManager = container
213: .getBean(AjaxFilterManager.class);
214: filterManager.addAjaxFilter(filter, name);
215: }
216: }
217:
218: /**
219: * Process the @DataTransferObject annotation on a given class
220: * @param clazz The class annotated with @DataTransferObject
221: * @param convertAnn The annotation
222: * @param container The IoC container to configure
223: * @throws InstantiationException If there are problems instantiating the Converter
224: * @throws IllegalAccessException If there are problems instantiating the Converter
225: */
226: protected void processConvert(Class<?> clazz,
227: DataTransferObject convertAnn, Container container)
228: throws InstantiationException, IllegalAccessException {
229: Class<? extends Converter> converter = convertAnn.converter();
230: String converterClass = converter.getName();
231: Map<String, String> params = getParamsMap(convertAnn.params());
232:
233: ConverterManager converterManager = container
234: .getBean(ConverterManager.class);
235: String converterName = converterClass.replace(".", "_");
236: converterManager
237: .addConverterType(converterName, converterClass);
238:
239: if (BeanConverter.class.isAssignableFrom(converter)) {
240: StringBuilder properties = new StringBuilder();
241: Set<Field> fields = new HashSet<Field>();
242: fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
243: fields.addAll(Arrays.asList(clazz.getFields()));
244: for (Field field : fields) {
245: if (field.getAnnotation(RemoteProperty.class) != null) {
246: properties.append(',').append(field.getName());
247: }
248: }
249:
250: for (Method method : clazz.getMethods()) {
251: if (method.getAnnotation(RemoteProperty.class) != null) {
252: String name = method.getName();
253: if (name.startsWith(METHOD_PREFIX_GET)
254: || name.startsWith(METHOD_PREFIX_IS)) {
255: if (name.startsWith(METHOD_PREFIX_GET)) {
256: name = name.substring(3);
257: } else {
258: name = name.substring(2);
259: }
260: name = Introspector.decapitalize(name);
261: properties.append(',').append(name);
262: }
263: }
264: }
265:
266: if (properties.length() > 0) {
267: properties.deleteCharAt(0);
268: params.put("include", properties.toString());
269: }
270: }
271:
272: converterManager.addConverter(clazz.getName(), converterName,
273: params);
274: }
275:
276: /**
277: * Global Filters apply to all classes
278: * @param clazz The class to use as a filter
279: * @param globalFilterAnn The filter annotation
280: * @param container The IoC container to configure
281: * @throws InstantiationException In case we can't create the given clazz
282: * @throws IllegalAccessException In case we can't create the given clazz
283: */
284: protected void processGlobalFilter(Class<?> clazz,
285: GlobalFilter globalFilterAnn, Container container)
286: throws InstantiationException, IllegalAccessException {
287: if (!AjaxFilter.class.isAssignableFrom(clazz)) {
288: throw new IllegalArgumentException(clazz.getName()
289: + " is not an AjaxFilter implementation");
290: }
291:
292: Map<String, String> filterParams = getParamsMap(globalFilterAnn
293: .params());
294: AjaxFilter filter = (AjaxFilter) clazz.newInstance();
295: if (filter != null) {
296: LocalUtil.setParams(filter, filterParams, null);
297: AjaxFilterManager filterManager = container
298: .getBean(AjaxFilterManager.class);
299: filterManager.addAjaxFilter(filter);
300: }
301: }
302:
303: /**
304: * Utility to turn a Param array into a Map<String, String>.
305: * @param params The params array from annotations
306: * @return A Map<String, String>
307: */
308: protected Map<String, String> getParamsMap(Param[] params) {
309: // TODO: Should we move this code into Param? Is that even possible?
310: Map<String, String> result = new HashMap<String, String>();
311: if (params != null) {
312: for (Param param : params) {
313: result.put(param.name(), param.value());
314: }
315: }
316: return result;
317: }
318:
319: /**
320: * The getter prefix for boolean variables
321: */
322: private static final String METHOD_PREFIX_IS = "is";
323:
324: /**
325: * The getter prefix for non-boolean variables
326: */
327: private static final String METHOD_PREFIX_GET = "get";
328:
329: /**
330: * The log stream
331: */
332: private static final Log log = LogFactory
333: .getLog(AnnotationsConfigurator.class);
334: }
|