001: /*
002: * Copyright 2005 Joe Walker
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.dwrp;
017:
018: import java.util.Arrays;
019: import java.util.Collection;
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027: import org.directwebremoting.extend.Converter;
028: import org.directwebremoting.extend.ConverterManager;
029: import org.directwebremoting.extend.InboundContext;
030: import org.directwebremoting.extend.InboundVariable;
031: import org.directwebremoting.extend.MarshallException;
032: import org.directwebremoting.extend.NamedConverter;
033: import org.directwebremoting.extend.NonNestedOutboundVariable;
034: import org.directwebremoting.extend.OutboundContext;
035: import org.directwebremoting.extend.OutboundVariable;
036: import org.directwebremoting.extend.TypeHintContext;
037: import org.directwebremoting.util.LocalUtil;
038: import org.directwebremoting.util.Messages;
039:
040: /**
041: * A class to manage the converter types and the instantiated class name matches.
042: * @author Joe Walker [joe at getahead dot ltd dot uk]
043: */
044: public class DefaultConverterManager implements ConverterManager {
045: /* (non-Javadoc)
046: * @see org.directwebremoting.ConverterManager#addConverterType(java.lang.String, java.lang.String)
047: */
048: public void addConverterType(String id, String className) {
049: if (!LocalUtil.isJavaIdentifier(id)) {
050: log.error("Illegal identifier: '" + id + "'");
051: return;
052: }
053:
054: Class<? extends Converter> clazz = LocalUtil.classForName(id,
055: className, Converter.class);
056: if (clazz != null) {
057: log.debug("- adding converter type: " + id + " = "
058: + clazz.getName());
059: converterTypes.put(id, clazz);
060: }
061: }
062:
063: /* (non-Javadoc)
064: * @see org.directwebremoting.ConverterManager#addConverter(java.lang.String, java.lang.String, java.util.Map)
065: */
066: public void addConverter(String match, String type,
067: Map<String, String> params)
068: throws IllegalArgumentException, InstantiationException,
069: IllegalAccessException {
070: Class<?> clazz = converterTypes.get(type);
071: if (clazz == null) {
072: log
073: .info("Probably not an issue: "
074: + match
075: + " is not available so the "
076: + type
077: + " converter will not load. This is only an problem if you wanted to use it.");
078: return;
079: }
080:
081: Converter converter = (Converter) clazz.newInstance();
082: LocalUtil.setParams(converter, params, ignore);
083:
084: // add the converter for the specified match
085: addConverter(match, converter);
086: }
087:
088: /* (non-Javadoc)
089: * @see org.directwebremoting.ConverterManager#addConverter(java.lang.String, org.directwebremoting.Converter)
090: */
091: public void addConverter(String match, Converter converter)
092: throws IllegalArgumentException {
093: // Check that we don't have this one already
094: Converter other = converters.get(match);
095: if (other != null) {
096: log.warn("Clash of converters for " + match + ". Using "
097: + converter.getClass().getName() + " in place of "
098: + other.getClass().getName());
099: }
100:
101: log.debug("- adding converter: "
102: + converter.getClass().getSimpleName() + " for "
103: + match);
104:
105: converter.setConverterManager(this );
106: converters.put(match, converter);
107: }
108:
109: /* (non-Javadoc)
110: * @see org.directwebremoting.ConverterManager#getConverterMatchStrings()
111: */
112: public Collection<String> getConverterMatchStrings() {
113: return Collections.unmodifiableSet(converters.keySet());
114: }
115:
116: /* (non-Javadoc)
117: * @see org.directwebremoting.ConverterManager#getConverterByMatchString(java.lang.String)
118: */
119: public Converter getConverterByMatchString(String match) {
120: return converters.get(match);
121: }
122:
123: /* (non-Javadoc)
124: * @see org.directwebremoting.ConverterManager#isConvertable(java.lang.Class)
125: */
126: public boolean isConvertable(Class<?> paramType) {
127: return getConverter(paramType) != null;
128: }
129:
130: /* (non-Javadoc)
131: * @see org.directwebremoting.ConverterManager#convertInbound(java.lang.Class, org.directwebremoting.InboundVariable, org.directwebremoting.InboundContext, org.directwebremoting.TypeHintContext)
132: */
133: public Object convertInbound(Class<?> paramType,
134: InboundVariable data, InboundContext inctx,
135: TypeHintContext incc) throws MarshallException {
136: Object converted = inctx.getConverted(data, paramType);
137: if (converted == null) {
138: Converter converter = null;
139:
140: // Was the inbound variable marshalled as an Object in the client
141: // (could mean that this is an instance of one of our generated
142: // JavaScript classes)
143: String type = data.getNamedObjectType();
144: if (type != null) {
145: converter = getNamedConverter(paramType, type);
146: }
147:
148: // Fall back to the standard way of locating a converter if we
149: // didn't find anything above
150: if (converter == null) {
151: converter = getConverter(paramType);
152: }
153:
154: if (converter == null) {
155: throw new MarshallException(
156: paramType,
157: Messages
158: .getString(
159: "DefaultConverterManager.MissingConverter",
160: paramType));
161: }
162:
163: // We only think about doing a null conversion ourselves once we are
164: // sure that there is a converter available. This prevents hackers
165: // from passing null to things they are not allowed to convert
166: if (data.isNull()) {
167: return null;
168: }
169:
170: inctx.pushContext(incc);
171: converted = converter
172: .convertInbound(paramType, data, inctx);
173: inctx.popContext();
174: }
175:
176: return converted;
177: }
178:
179: /* (non-Javadoc)
180: * @see org.directwebremoting.ConverterManager#convertOutbound(java.lang.Object, org.directwebremoting.OutboundContext)
181: */
182: public OutboundVariable convertOutbound(Object data,
183: OutboundContext converted) throws MarshallException {
184: if (data == null) {
185: return new NonNestedOutboundVariable("null");
186: }
187:
188: // Check to see if we have done this one already
189: OutboundVariable ov = converted.get(data);
190: if (ov != null) {
191: // So the object as been converted already, we just need to refer to it.
192: return ov.getReferenceVariable();
193: }
194:
195: // So we will have to do the conversion
196: Converter converter = getConverter(data);
197: if (converter == null) {
198: String message = Messages.getString(
199: "DefaultConverterManager.MissingConverter", data
200: .getClass().getName());
201: log.error(message);
202: return new ErrorOutboundVariable(message);
203: }
204:
205: return converter.convertOutbound(data, converted);
206: }
207:
208: /* (non-Javadoc)
209: * @see org.directwebremoting.ConverterManager#setExtraTypeInfo(org.directwebremoting.TypeHintContext, java.lang.Class)
210: */
211: public void setExtraTypeInfo(TypeHintContext thc, Class<?> type) {
212: extraTypeInfoMap.put(thc, type);
213: }
214:
215: /* (non-Javadoc)
216: * @see org.directwebremoting.ConverterManager#getExtraTypeInfo(org.directwebremoting.TypeHintContext)
217: */
218: public Class<?> getExtraTypeInfo(TypeHintContext thc) {
219: return extraTypeInfoMap.get(thc);
220: }
221:
222: /* (non-Javadoc)
223: * @see org.directwebremoting.ConverterManager#setConverters(java.util.Map)
224: */
225: public void setConverters(Map<String, Converter> converters) {
226: this .converters = converters;
227: }
228:
229: /**
230: * Like <code>getConverter(object.getClass());</code> except that since the
231: * object can be null we check for that fist and then do a lookup against
232: * the <code>Void.TYPE</code> converter
233: * @param object The object to find a converter for
234: * @return The converter for the given type
235: */
236: private Converter getConverter(Object object) {
237: if (object == null) {
238: return getConverter(Void.TYPE);
239: }
240:
241: return getConverter(object.getClass());
242: }
243:
244: /**
245: * When we are using typed Javascript names we sometimes want to get a
246: * specially named converter
247: * @param paramType The class that we are converting to
248: * @param javascriptClassName The type name as passed in from the client
249: * @return The Converter that matches this request (if any)
250: * @throws MarshallException IF marshalling fails
251: */
252: protected Converter getNamedConverter(Class<?> paramType,
253: String javascriptClassName) throws MarshallException {
254: // Locate a converter for this JavaScript classname
255: for (Map.Entry<String, Converter> entry : converters.entrySet()) {
256: String match = entry.getKey();
257: Converter conv = entry.getValue();
258:
259: // JavaScript mapping is only applicable for compound converters
260: if (conv instanceof NamedConverter) {
261: NamedConverter boConv = (NamedConverter) conv;
262: if (boConv.getJavascript() != null
263: && boConv.getJavascript().equals(
264: javascriptClassName)) {
265: // We found a potential converter! But is the converter's
266: // Java class compatible with the parameter type?
267: try {
268: Class<?> inboundClass = LocalUtil
269: .classForName(match);
270: if (paramType.isAssignableFrom(inboundClass)) {
271: // Hack: We also want to make sure that the
272: // converter creates its object based on the inbound
273: // class instead of the parameter type, and we have
274: // to use the other reference for this:
275: boConv.setInstanceType(inboundClass);
276: return boConv;
277: }
278: } catch (ClassNotFoundException ex) {
279: throw new MarshallException(paramType, ex);
280: }
281: }
282: }
283: }
284:
285: return null;
286: }
287:
288: /**
289: * @param paramType The type to find a converter for
290: * @return The converter for the given type, or null if one can't be found
291: */
292: private Converter getConverter(Class<?> paramType) {
293: // Can we find a converter assignable to paramType in the HashMap?
294: Converter converter = getConverterAssignableFrom(paramType);
295: if (converter != null) {
296: return converter;
297: }
298:
299: String lookup = paramType.getName();
300:
301: // Before we start trying for a match on package parts we check for
302: // dynamic proxies
303: if (lookup.startsWith("$Proxy")) {
304: converter = converters.get("$Proxy*");
305: if (converter != null) {
306: return converter;
307: }
308: }
309:
310: while (true) {
311: // Can we find a converter using wildcards?
312: converter = converters.get(lookup + ".*");
313: if (converter != null) {
314: return converter;
315: }
316:
317: // Arrays can have wildcards like [L* so we don't require a '.'
318: converter = converters.get(lookup + '*');
319: if (converter != null) {
320: return converter;
321: }
322:
323: // Give up if the name is now empty
324: if (lookup.length() == 0) {
325: break;
326: }
327:
328: // Strip of the component after the last .
329: int lastdot = lookup.lastIndexOf('.');
330: if (lastdot != -1) {
331: lookup = lookup.substring(0, lastdot);
332: } else {
333: int arrayMarkers = 0;
334: while (lookup.charAt(arrayMarkers) == '[') {
335: arrayMarkers++;
336: }
337:
338: if (arrayMarkers == 0) {
339: // so we are out of dots and out of array markers
340: // bail out.
341: break;
342: }
343:
344: // We want to keep the type marker too
345: lookup = lookup.substring(arrayMarkers - 1,
346: arrayMarkers + 1);
347:
348: // Now can we find it?
349: converter = converters.get(lookup);
350: if (converter != null) {
351: return converter;
352: }
353: }
354: }
355:
356: return null;
357: }
358:
359: /**
360: * @param paramType The type to find a converter for
361: * @return The converter assignable for the given type, or null if one can't be found
362: */
363: private Converter getConverterAssignableFrom(Class<?> paramType) {
364: if (paramType == null) {
365: return null;
366: }
367:
368: String lookup = paramType.getName();
369:
370: // Can we find the converter for paramType in the converters HashMap?
371: Converter converter = converters.get(lookup);
372: if (converter != null) {
373: return converter;
374: }
375:
376: // Lookup all of the interfaces of this class for a match
377: for (Class<?> anInterface : paramType.getInterfaces()) {
378: converter = getConverterAssignableFrom(anInterface);
379: if (converter != null) {
380: converters.put(lookup, converter);
381: return converter;
382: }
383: }
384:
385: // Let's search it in paramType superClass
386: converter = getConverterAssignableFrom(paramType
387: .getSuperclass());
388: if (converter != null) {
389: converters.put(lookup, converter);
390: }
391:
392: return converter;
393: }
394:
395: /**
396: * Where we store real type information behind generic types
397: */
398: protected Map<TypeHintContext, Class<?>> extraTypeInfoMap = new HashMap<TypeHintContext, Class<?>>();
399:
400: /**
401: * The list of the available converters
402: */
403: protected Map<String, Class<?>> converterTypes = new HashMap<String, Class<?>>();
404:
405: /**
406: * The list of the configured converters
407: */
408: protected Map<String, Converter> converters = new HashMap<String, Converter>();
409:
410: /**
411: * The properties that we don't warn about if they don't exist.
412: * @see DefaultConverterManager#addConverter(String, String, Map)
413: */
414: private static List<String> ignore = Arrays.asList("converter",
415: "match");
416:
417: /**
418: * The log stream
419: */
420: private static final Log log = LogFactory
421: .getLog(DefaultConverterManager.class);
422: }
|