001: /*
002: * Copyright (C) The DNA Group. All rights reserved.
003: *
004: * This software is published under the terms of the DNA
005: * Software License version 1.1, a copy of which has been included
006: * with this distribution in the LICENSE.txt file.
007: */
008: package org.codehaus.dna.tools.verifier;
009:
010: import java.lang.reflect.Method;
011: import java.lang.reflect.Modifier;
012: import java.net.URL;
013: import java.text.MessageFormat;
014: import java.util.ArrayList;
015: import java.util.List;
016: import java.util.ResourceBundle;
017:
018: import org.codehaus.dna.Active;
019: import org.codehaus.dna.Composable;
020: import org.codehaus.dna.Configurable;
021: import org.codehaus.dna.Configuration;
022: import org.codehaus.dna.LogEnabled;
023: import org.codehaus.dna.ResourceLocator;
024: import org.codehaus.metaclass.Attributes;
025: import org.codehaus.metaclass.model.Attribute;
026:
027: /**
028: * Utility class to help verify that component respects the
029: * rules of an DNA component.
030: *
031: * @author Peter Donald
032: * @version $Revision: 1.2 $ $Date: 2004/05/01 09:51:48 $
033: */
034: public class ComponentVerifier {
035: /**
036: * Key used to look up ResourceBundle.
037: */
038: private static final String BASE_NAME = ComponentVerifier.class
039: .getName()
040: + "Resources";
041:
042: /**
043: * The resource bundle.
044: */
045: private static final ResourceBundle BUNDLE = ResourceBundle
046: .getBundle(BASE_NAME);
047:
048: /**
049: * Constant for array of 0 classes. Saves recreating array everytime
050: * look up constructor with no args.
051: */
052: private static final Class[] EMPTY_TYPES = new Class[0];
053:
054: /**
055: * The interfaces representing lifecycle stages.
056: */
057: private static final Class[] FRAMEWORK_CLASSES = new Class[] {
058: LogEnabled.class, Composable.class, Configurable.class,
059: Active.class };
060:
061: /**
062: * The name of the Configurable.configure() method.
063: */
064: private static final String CONFIGURE_METHOD_NAME = "configure";
065:
066: /**
067: * The parameter types of the Configurable.configure() method.
068: */
069: private static final Class[] CONFIGURE_PARAMETER_TYPES = new Class[] { Configuration.class };
070:
071: /**
072: * The name of the Composable.compose() method.
073: */
074: private static final String COMPOSE_METHOD_NAME = "compose";
075:
076: /**
077: * The parameter types of the Composable.compose() method.
078: */
079: private static final Class[] COMPOSE_PARAMETER_TYPES = new Class[] { ResourceLocator.class };
080:
081: /**
082: * Verfiy that specified components designate classes that implement the
083: * advertised interfaces.
084: *
085: * @param type the component type
086: * @return an array of issues
087: */
088: public VerifyIssue[] verifyType(final Class type) {
089: final List issues = new ArrayList();
090: verifyMetaData(type, issues);
091: verifyDependencyMetaData(type, issues);
092: verifyConfigurationMetaData(type, issues);
093:
094: final Class[] interfaces = getServiceClasses(type, issues);
095:
096: verifyClass(type, issues);
097: verifyImplementsServices(type, interfaces, issues);
098: for (int i = 0; i < interfaces.length; i++) {
099: verifyService(interfaces[i], issues);
100: }
101:
102: return (VerifyIssue[]) issues.toArray(new VerifyIssue[issues
103: .size()]);
104: }
105:
106: /**
107: * Verify that the component is annotated with the
108: * dna.component metadata.
109: *
110: * @param type the type
111: * @param issues the list of issues
112: */
113: void verifyMetaData(final Class type, final List issues) {
114: final Attribute attribute = Attributes.getAttribute(type,
115: "dna.component");
116: if (null == attribute) {
117: final String message = getMessage("CV001");
118: final VerifyIssue issue = new VerifyIssue(
119: VerifyIssue.ERROR, message);
120: issues.add(issue);
121: }
122: }
123:
124: /**
125: * Verify that the configuration metadata for component is valid.
126: *
127: * @param type the type
128: * @param issues the list of issues
129: */
130: void verifyConfigurationMetaData(final Class type, final List issues) {
131: Attribute attribute = getConfigurationMetaData(type);
132: if (null != attribute) {
133: final String location = attribute.getParameter("location");
134: if (null == location) {
135: final Object[] args = new Object[] { "type" };
136: final String message = getMessage("CV019", args);
137: final VerifyIssue issue = new VerifyIssue(
138: VerifyIssue.ERROR, message);
139: issues.add(issue);
140: } else {
141: verifyLocation(type, location, issues);
142: }
143: }
144: }
145:
146: /**
147: * Return the configuration metadata for component if any.
148: * Protected to allow overiding in subclasses.
149: *
150: * @param type the component type
151: * @return the metadata if any
152: */
153: protected Attribute getConfigurationMetaData(final Class type) {
154: try {
155: final Method method = getConfigurationMethod(type);
156: return Attributes.getAttribute(method, "dna.configuration");
157: } catch (final NoSuchMethodException nsme) {
158: return null;
159: }
160: }
161:
162: /**
163: * Return the method used to pass configuration to component.
164: *
165: * @param type the components type
166: * @return the method
167: * @throws NoSuchMethodException if unable to locate method
168: */
169: protected Method getConfigurationMethod(final Class type)
170: throws NoSuchMethodException {
171: if (!Configurable.class.isAssignableFrom(type)) {
172: throw new NoSuchMethodException();
173: }
174: return type.getMethod(CONFIGURE_METHOD_NAME,
175: CONFIGURE_PARAMETER_TYPES);
176: }
177:
178: /**
179: * Verify that the location specified for the schema actually exists.
180: *
181: * @param type the component type
182: * @param location the location of schmea
183: * @param issues the list of issues
184: */
185: void verifyLocation(final Class type, final String location,
186: final List issues) {
187: final URL url = type.getResource(location);
188: if (null == url) {
189: final Object[] args = new Object[] { location };
190: final String message = getMessage("CV020", args);
191: final VerifyIssue issue = new VerifyIssue(
192: VerifyIssue.ERROR, message);
193: issues.add(issue);
194: }
195: }
196:
197: /**
198: * Verify that the dependency metadata for component is valid.
199: *
200: * @param type the type
201: * @param issues the list of issues
202: */
203: void verifyDependencyMetaData(final Class type, final List issues) {
204: final Attribute[] attributes = getDependencyAttributes(type);
205: for (int i = 0; i < attributes.length; i++) {
206: final Attribute attribute = attributes[i];
207: verifyDependencyMetaData(type, attribute, issues);
208: }
209: }
210:
211: /**
212: * Return the dependency attributes for component.
213: * Made protected so that it is possible to overload
214: * in subclasses.
215: *
216: * @param type the component type
217: * @return the dependency attributes
218: */
219: protected Attribute[] getDependencyAttributes(final Class type) {
220: try {
221: final Method method = getComposeMethod(type);
222: return Attributes.getAttributes(method, "dna.dependency");
223: } catch (NoSuchMethodException e) {
224: return Attribute.EMPTY_SET;
225: }
226: }
227:
228: /**
229: * Return the method via which component is passed services.
230: * This method is protected so can overload in subclasses
231: * and get alternative methods.
232: *
233: * @param type the components type
234: * @return the method
235: * @throws NoSuchMethodException if can not locate method
236: */
237: protected Method getComposeMethod(final Class type)
238: throws NoSuchMethodException {
239: if (!Composable.class.isAssignableFrom(type)) {
240: throw new NoSuchMethodException();
241: }
242: return type.getMethod(COMPOSE_METHOD_NAME,
243: COMPOSE_PARAMETER_TYPES);
244: }
245:
246: /**
247: * Verify that the dependency metadata tag is valid.
248: *
249: * @param type the component type
250: * @param attribute the metadata tag
251: * @param issues the list of issues
252: */
253: void verifyDependencyMetaData(final Class type,
254: final Attribute attribute, final List issues) {
255: final String optional = attribute.getParameter("optional");
256: verifyOptionalParameter(optional, issues);
257:
258: final String typeName = attribute.getParameter("type");
259: if (null == typeName) {
260: final Object[] args = new Object[] { "type" };
261: final String message = getMessage("CV015", args);
262: final VerifyIssue issue = new VerifyIssue(
263: VerifyIssue.ERROR, message);
264: issues.add(issue);
265: } else {
266: verifyDependencyType(type, typeName, issues);
267: final String key = attribute.getParameter("key");
268: if (null == key) {
269: final Object[] args = new Object[] { "key" };
270: final String message = getMessage("CV015", args);
271: final VerifyIssue issue = new VerifyIssue(
272: VerifyIssue.ERROR, message);
273: issues.add(issue);
274: } else {
275: verifyDependencyKeyConforms(typeName, key, issues);
276: }
277: }
278: }
279:
280: /**
281: * Verify optional parameter for dependency metadata.
282: *
283: * @param optional the value of parameter
284: * @param issues the list of issues
285: */
286: void verifyOptionalParameter(final String optional,
287: final List issues) {
288: if (null == optional) {
289: final Object[] args = new Object[] { "optional" };
290: final String message = getMessage("CV015", args);
291: final VerifyIssue issue = new VerifyIssue(
292: VerifyIssue.ERROR, message);
293: issues.add(issue);
294: } else {
295: verifyDependencyOptionalValid(optional, issues);
296: }
297: }
298:
299: /**
300: * Verify that value of optional value is valid.
301: *
302: * @param optional the optional value
303: * @param issues the list of issues
304: */
305: void verifyDependencyOptionalValid(final String optional,
306: final List issues) {
307: if (!optional.equals("true") && !optional.equals("false")) {
308: final Object[] args = new Object[] { optional };
309: final String message = getMessage("CV018", args);
310: final VerifyIssue issue = new VerifyIssue(
311: VerifyIssue.ERROR, message);
312: issues.add(issue);
313: }
314: }
315:
316: /**
317: * Verify that the key conforms to the expectation
318: * of being (type)[/(qualifier)]
319: *
320: * @param typeName the name of dependency type
321: * @param key the dependency key
322: * @param issues the list of issues
323: */
324: void verifyDependencyKeyConforms(final String typeName,
325: final String key, final List issues) {
326: final int typeLength = typeName.length();
327: final int keyLength = key.length();
328: final String prefix;
329: if (typeLength == keyLength) {
330: prefix = typeName;
331: } else {
332: prefix = typeName + "/";
333: }
334: if (!key.startsWith(prefix)) {
335: final Object[] args = new Object[] { key };
336: final String message = getMessage("CV017", args);
337: final VerifyIssue issue = new VerifyIssue(
338: VerifyIssue.NOTICE, message);
339: issues.add(issue);
340: }
341: }
342:
343: /**
344: * Verify that the type specified dependency can be loaded.
345: *
346: * @param type the component type
347: * @param typeName the type of dependency
348: * @param issues the list of issues
349: */
350: void verifyDependencyType(final Class type, final String typeName,
351: final List issues) {
352: try {
353: type.getClassLoader().loadClass(typeName);
354: } catch (final Throwable t) {
355: final Object[] args = new Object[] { typeName, t };
356: final String message = getMessage("CV016", args);
357: final VerifyIssue issue = new VerifyIssue(
358: VerifyIssue.ERROR, message);
359: issues.add(issue);
360: }
361: }
362:
363: /**
364: * Verify that the supplied implementation implements the specified
365: * services.
366: *
367: * @param implementation the class representign component
368: * @param services the services that the implementation must provide
369: * @param issues the list of issues
370: */
371: void verifyImplementsServices(final Class implementation,
372: final Class[] services, final List issues) {
373: for (int i = 0; i < services.length; i++) {
374: if (!services[i].isAssignableFrom(implementation)) {
375: final Object[] args = new Object[] { services[i]
376: .getName() };
377: final String message = getMessage("CV002", args);
378: final VerifyIssue issue = new VerifyIssue(
379: VerifyIssue.ERROR, message);
380: issues.add(issue);
381: }
382: }
383: }
384:
385: /**
386: * Verify that the supplied class is a valid type for
387: * a component.
388: *
389: * @param type the class representing component
390: * @param issues the list of issues
391: */
392: void verifyClass(final Class type, final List issues) {
393: verifyNoArgConstructor(type, issues);
394: verifyNonAbstract(type, issues);
395: verifyNonArray(type, issues);
396: verifyNonInterface(type, issues);
397: verifyNonPrimitive(type, issues);
398: verifyPublic(type, issues);
399: }
400:
401: /**
402: * Verify that the supplied class is a valid class for
403: * a service.
404: *
405: * @param clazz the class representign service
406: * @param issues the list of issues
407: */
408: void verifyService(final Class clazz, final List issues) {
409: verifyServiceIsaInterface(clazz, issues);
410: verifyServiceIsPublic(clazz, issues);
411: verifyServiceNotALifecycle(clazz, issues);
412: }
413:
414: /**
415: * Verify that the service implemented by
416: * specified component is an interface.
417: *
418: * @param clazz the class representign service
419: * @param issues the list of issues
420: */
421: void verifyServiceIsaInterface(final Class clazz, final List issues) {
422: if (!clazz.isInterface()) {
423: final Object[] args = new Object[] { clazz.getName() };
424: final String message = getMessage("CV004", args);
425: final VerifyIssue issue = new VerifyIssue(
426: VerifyIssue.ERROR, message);
427: issues.add(issue);
428: }
429: }
430:
431: /**
432: * Verify that the service implemented by
433: * specified component is public.
434: *
435: * @param clazz the class representign service
436: * @param issues the list of issues
437: */
438: void verifyServiceIsPublic(final Class clazz, final List issues) {
439: final boolean isPublic = Modifier
440: .isPublic(clazz.getModifiers());
441: if (!isPublic) {
442: final Object[] args = new Object[] { clazz.getName() };
443: final String message = getMessage("CV005", args);
444: final VerifyIssue issue = new VerifyIssue(
445: VerifyIssue.ERROR, message);
446: issues.add(issue);
447: }
448: }
449:
450: /**
451: * Verify that the service implemented by
452: * specified component does not extend any lifecycle interfaces.
453: *
454: * @param clazz the class representign service
455: * @param issues the list of issues
456: */
457: void verifyServiceNotALifecycle(final Class clazz, final List issues) {
458: for (int i = 0; i < FRAMEWORK_CLASSES.length; i++) {
459: final Class lifecycle = FRAMEWORK_CLASSES[i];
460: if (lifecycle.isAssignableFrom(clazz)) {
461: final Object[] args = new Object[] { clazz.getName(),
462: lifecycle.getName() };
463: final String message = getMessage("CV006", args);
464: final VerifyIssue issue = new VerifyIssue(
465: VerifyIssue.ERROR, message);
466: issues.add(issue);
467: }
468: }
469: }
470:
471: /**
472: * Verify that the component has a no-arg aka default
473: * constructor.
474: *
475: * @param clazz the class representign component
476: * @param issues the list of issues
477: */
478: void verifyNoArgConstructor(final Class clazz, final List issues) {
479: try {
480: clazz.getConstructor(EMPTY_TYPES);
481: } catch (final NoSuchMethodException nsme) {
482: final String message = getMessage("CV008");
483: final VerifyIssue issue = new VerifyIssue(
484: VerifyIssue.ERROR, message);
485: issues.add(issue);
486: }
487: }
488:
489: /**
490: * Verify that the component is not represented by
491: * abstract class.
492: *
493: * @param clazz the class representign component
494: * @param issues the list of issues
495: */
496: void verifyNonAbstract(final Class clazz, final List issues) {
497: final boolean isAbstract = Modifier.isAbstract(clazz
498: .getModifiers());
499: if (isAbstract) {
500: final String message = getMessage("CV009");
501: final VerifyIssue issue = new VerifyIssue(
502: VerifyIssue.ERROR, message);
503: issues.add(issue);
504: }
505: }
506:
507: /**
508: * Verify that the component is not represented by
509: * abstract class.
510: *
511: * @param clazz the class representign component
512: * @param issues the list of issues
513: */
514: void verifyPublic(final Class clazz, final List issues) {
515: final boolean isPublic = Modifier
516: .isPublic(clazz.getModifiers());
517: if (!isPublic) {
518: final String message = getMessage("CV010");
519: final VerifyIssue issue = new VerifyIssue(
520: VerifyIssue.ERROR, message);
521: issues.add(issue);
522: }
523: }
524:
525: /**
526: * Verify that the component is not represented by
527: * primitive class.
528: *
529: * @param clazz the class representign component
530: * @param issues the list of issues
531: */
532: void verifyNonPrimitive(final Class clazz, final List issues) {
533: if (clazz.isPrimitive()) {
534: final String message = getMessage("CV011");
535: final VerifyIssue issue = new VerifyIssue(
536: VerifyIssue.ERROR, message);
537: issues.add(issue);
538: }
539: }
540:
541: /**
542: * Verify that the component is not represented by
543: * interface class.
544: *
545: * @param clazz the class representign component
546: * @param issues the list of issues
547: */
548: void verifyNonInterface(final Class clazz, final List issues) {
549: if (clazz.isInterface()) {
550: final String message = getMessage("CV012");
551: final VerifyIssue issue = new VerifyIssue(
552: VerifyIssue.ERROR, message);
553: issues.add(issue);
554: }
555: }
556:
557: /**
558: * Verify that the component is not represented by
559: * an array class.
560: *
561: * @param clazz the class representign component
562: * @param issues the list of issues
563: */
564: void verifyNonArray(final Class clazz, final List issues) {
565: if (clazz.isArray()) {
566: final String message = getMessage("CV013");
567: final VerifyIssue issue = new VerifyIssue(
568: VerifyIssue.ERROR, message);
569: issues.add(issue);
570: }
571: }
572:
573: /**
574: * Retrieve an array of Classes for all the services that a Component
575: * offers. This method also makes sure all services offered are
576: * interfaces.
577: *
578: * @param type the component type
579: * @param issues the list of issues
580: * @return an array of Classes for all the services
581: */
582: Class[] getServiceClasses(final Class type, final List issues) {
583: final List services = new ArrayList();
584: final ClassLoader classLoader = type.getClassLoader();
585: final Attribute[] attributes = Attributes.getAttributes(type,
586: "dna.service");
587: for (int i = 0; i < attributes.length; i++) {
588: final String classname = attributes[i].getParameter("type");
589: try {
590: final Class clazz = classLoader.loadClass(classname);
591: services.add(clazz);
592: } catch (final Throwable t) {
593: final Object[] args = new Object[] { classname, t };
594: final String message = getMessage("CV014", args);
595: final VerifyIssue issue = new VerifyIssue(
596: VerifyIssue.ERROR, message);
597: issues.add(issue);
598: }
599: }
600:
601: return (Class[]) services.toArray(new Class[services.size()]);
602: }
603:
604: /**
605: * Get message out of resource bundle with specified key.
606: *
607: * @param key the key
608: * @return the message
609: */
610: String getMessage(final String key) {
611: return BUNDLE.getString(key);
612: }
613:
614: /**
615: * Get message out of resource bungle with specified key
616: * and format wit specified arguments.
617: *
618: * @param key the keys
619: * @param args the arguments
620: * @return the message
621: */
622: String getMessage(final String key, final Object[] args) {
623: final String pattern = getMessage(key);
624: return MessageFormat.format(pattern, args);
625: }
626: }
|