1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 package com.sun.jersey.wadl.resourcedoc;
38
39 import com.atlassian.plugins.rest.common.expand.EntityCrawler;
40 import com.atlassian.plugins.rest.common.expand.SelfExpandingExpander;
41 import com.atlassian.plugins.rest.common.expand.parameter.DefaultExpandParameter;
42 import com.atlassian.plugins.rest.common.expand.resolver.ChainingEntityExpanderResolver;
43 import com.atlassian.plugins.rest.common.expand.resolver.CollectionEntityExpanderResolver;
44 import com.atlassian.plugins.rest.common.expand.resolver.EntityExpanderResolver;
45 import com.atlassian.plugins.rest.common.expand.resolver.ExpandConstraintEntityExpanderResolver;
46 import com.atlassian.plugins.rest.common.expand.resolver.IdentityEntityExpanderResolver;
47 import com.atlassian.plugins.rest.common.expand.resolver.ListWrapperEntityExpanderResolver;
48 import com.atlassian.plugins.rest.common.json.DefaultJaxbJsonMarshaller;
49 import com.google.common.collect.Lists;
50 import com.sun.javadoc.AnnotationDesc;
51 import com.sun.javadoc.AnnotationDesc.ElementValuePair;
52 import com.sun.javadoc.ClassDoc;
53 import com.sun.javadoc.DocErrorReporter;
54 import com.sun.javadoc.MemberDoc;
55 import com.sun.javadoc.MethodDoc;
56 import com.sun.javadoc.ParamTag;
57 import com.sun.javadoc.Parameter;
58 import com.sun.javadoc.RootDoc;
59 import com.sun.javadoc.SeeTag;
60 import com.sun.javadoc.Tag;
61 import com.sun.jersey.server.wadl.generators.resourcedoc.model.AnnotationDocType;
62 import com.sun.jersey.server.wadl.generators.resourcedoc.model.ClassDocType;
63 import com.sun.jersey.server.wadl.generators.resourcedoc.model.MethodDocType;
64 import com.sun.jersey.server.wadl.generators.resourcedoc.model.NamedValueType;
65 import com.sun.jersey.server.wadl.generators.resourcedoc.model.ParamDocType;
66 import com.sun.jersey.server.wadl.generators.resourcedoc.model.RepresentationDocType;
67 import com.sun.jersey.server.wadl.generators.resourcedoc.model.RequestDocType;
68 import com.sun.jersey.server.wadl.generators.resourcedoc.model.ResourceDocType;
69 import com.sun.jersey.server.wadl.generators.resourcedoc.model.ResponseDocType;
70 import com.sun.jersey.server.wadl.generators.resourcedoc.model.WadlParamType;
71 import org.apache.commons.lang.StringUtils;
72 import org.apache.xml.serialize.OutputFormat;
73 import org.apache.xml.serialize.XMLSerializer;
74
75 import java.io.BufferedOutputStream;
76 import java.io.File;
77 import java.io.FileOutputStream;
78 import java.io.IOException;
79 import java.io.OutputStream;
80 import java.lang.reflect.Array;
81 import java.lang.reflect.Field;
82 import java.net.MalformedURLException;
83 import java.net.URL;
84 import java.net.URLClassLoader;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.Collection;
88 import java.util.HashMap;
89 import java.util.Iterator;
90 import java.util.List;
91 import java.util.Map;
92 import java.util.Map.Entry;
93 import java.util.logging.Level;
94 import java.util.logging.Logger;
95 import java.util.regex.Matcher;
96 import java.util.regex.Pattern;
97 import javax.xml.bind.JAXBContext;
98 import javax.xml.bind.JAXBException;
99 import javax.xml.bind.Marshaller;
100 import javax.xml.namespace.QName;
101
102 import static java.util.Collections.emptyList;
103
104
105
106
107
108
109
110
111
112
113 public class ResourceDocletJSON
114 {
115
116 private static final Pattern PATTERN_RESPONSE_REPRESENATION = Pattern.compile( "@response\\.representation\\.([\\d]+)\\..*" );
117 private static final String OPTION_OUTPUT = "-output";
118 private static final String OPTION_CLASSPATH = "-classpath";
119 private static final String OPTION_DOC_PROCESSORS = "-processors";
120
121 private static final Logger LOG = Logger.getLogger( ResourceDocletJSON.class
122 .getName() );
123
124 private static final String OPTION_EXPAND = "-expand";
125
126
127
128
129
130
131
132 public static boolean start( RootDoc root ) {
133 final String output = getOptionArg( root.options(), OPTION_OUTPUT );
134
135 final String classpath = getOptionArg( root.options(), OPTION_CLASSPATH );
136
137 final String[] classpathElements = classpath.split( ":" );
138
139 final String expandString = getOptionArg( root.options(), OPTION_EXPAND );
140 Collection<String> expand = emptyList();
141 if (expandString != null) {
142 expand = Arrays.asList(StringUtils.split(expandString, '&'));
143 }
144
145 final ClassLoader cl = Thread.currentThread().getContextClassLoader();
146 final ClassLoader ncl = new Loader( classpathElements,
147 ResourceDocletJSON.class.getClassLoader() );
148 Thread.currentThread().setContextClassLoader( ncl );
149
150
151 final String docProcessorOption = getOptionArg( root.options(), OPTION_DOC_PROCESSORS );
152 final String[] docProcessors = docProcessorOption != null ? docProcessorOption.split( ":" ) : null;
153 final DocProcessorWrapper docProcessor = new DocProcessorWrapper();
154 try {
155 if ( docProcessors != null && docProcessors.length > 0 ) {
156 final Class<?> clazz = Class.forName( docProcessors[0], true, Thread.currentThread().getContextClassLoader() );
157 final Class<? extends DocProcessor> dpClazz = clazz.asSubclass( DocProcessor.class );
158 docProcessor.add( dpClazz.newInstance() );
159 }
160 } catch ( Exception e ) {
161 LOG.log( Level.SEVERE, "Could not load docProcessors " + docProcessorOption, e );
162 }
163
164 try {
165 final ResourceDocType result = new ResourceDocType();
166
167 final ClassDoc[] classes = root.classes();
168 for ( ClassDoc classDoc : classes ) {
169 LOG.fine( "Writing class " + classDoc.qualifiedTypeName() );
170 final ClassDocType classDocType = new ClassDocType();
171 classDocType.setClassName( classDoc.qualifiedTypeName() );
172 classDocType.setCommentText( classDoc.commentText() );
173 docProcessor.processClassDoc( classDoc, classDocType );
174
175 for ( MethodDoc methodDoc : classDoc.methods() ) {
176
177 final MethodDocType methodDocType = new MethodDocType();
178 methodDocType.setMethodName( methodDoc.name() );
179 methodDocType.setCommentText( methodDoc.commentText() );
180 docProcessor.processMethodDoc( methodDoc, methodDocType );
181
182 addParamDocs( methodDoc, methodDocType, docProcessor );
183
184 addRequestRepresentationDoc( methodDoc, methodDocType, expand);
185
186 addResponseDoc( methodDoc, methodDocType, expand);
187
188 classDocType.getMethodDocs().add( methodDocType );
189 }
190
191 result.getDocs().add( classDocType );
192 }
193
194 try {
195 final Class<?>[] clazzes = getJAXBContextClasses( result, docProcessor );
196 final JAXBContext c = JAXBContext.newInstance( clazzes );
197 final Marshaller m = c.createMarshaller();
198 m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
199 final OutputStream out = new BufferedOutputStream( new FileOutputStream( output ) );
200
201
202 final String[] cdataElements = getCDataElements( docProcessor );
203 final XMLSerializer serializer = getXMLSerializer( out, cdataElements );
204
205 m.marshal( result, serializer );
206 out.close();
207
208 LOG.info( "Wrote " + output );
209
210 } catch (Exception e) {
211 LOG.log( Level.SEVERE, "Could not serialize ResourceDoc.", e );
212 return false;
213 }
214 } finally {
215 Thread.currentThread().setContextClassLoader( cl );
216 }
217
218 return true;
219 }
220
221 private static String[] getCDataElements( DocProcessor docProcessor ) {
222 final String[] original = new String[] { "ns1^commentText", "ns2^commentText", "^commentText" };
223 if ( docProcessor == null ) {
224 return original;
225 }
226 else {
227 final String[] cdataElements = docProcessor.getCDataElements();
228 if ( cdataElements == null || cdataElements.length == 0 ) {
229 return original;
230 }
231 else {
232
233 final String[] result = copyOf( original, original.length + cdataElements.length );
234 for ( int i = 0; i < cdataElements.length; i++ ) {
235 result[ original.length + i ] = cdataElements[i];
236 }
237 return result;
238 }
239 }
240 }
241
242 @SuppressWarnings("unchecked")
243 private static <T,U> T[] copyOf( U[] original, int newLength ) {
244 final T[] copy = ((Object)original.getClass() == (Object)Object[].class)
245 ? (T[]) new Object[newLength]
246 : (T[]) Array.newInstance(original.getClass().getComponentType(), newLength);
247 System.arraycopy(original, 0, copy, 0,
248 Math.min(original.length, newLength));
249 return copy;
250 }
251
252 private static Class<?>[] getJAXBContextClasses(
253 final ResourceDocType result, DocProcessor docProcessor ) {
254 final Class<?>[] clazzes;
255 if ( docProcessor == null ) {
256 clazzes = new Class<?>[1];
257 }
258 else {
259 final Class<?>[] requiredJaxbContextClasses = docProcessor.getRequiredJaxbContextClasses();
260 if ( requiredJaxbContextClasses != null ) {
261 clazzes = new Class<?>[1 + requiredJaxbContextClasses.length ];
262 for ( int i = 0; i < requiredJaxbContextClasses.length; i++ ) {
263 clazzes[i + 1] = requiredJaxbContextClasses[i];
264 }
265 }
266 else {
267 clazzes = new Class<?>[1];
268 }
269 }
270 clazzes[0] = result.getClass();
271 return clazzes;
272 }
273
274 private static XMLSerializer getXMLSerializer( OutputStream os, String[] cdataElements ) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
275
276 OutputFormat of = new OutputFormat();
277
278
279
280
281
282
283 of.setCDataElements( cdataElements );
284
285
286 of.setPreserveSpace(true);
287 of.setIndenting(true);
288
289
290 XMLSerializer serializer = new XMLSerializer(of);
291
292 serializer.setOutputByteStream( os );
293
294 return serializer;
295 }
296
297 private static void addResponseDoc(MethodDoc methodDoc,
298 final MethodDocType methodDocType, Collection<String> expand) {
299
300 final ResponseDocType responseDoc = new ResponseDocType();
301
302 final Tag returnTag = getSingleTagOrNull( methodDoc, "return" );
303 if ( returnTag != null ) {
304 responseDoc.setReturnDoc( returnTag.text() );
305 }
306
307 final Tag[] responseParamTags = methodDoc.tags( "response.param" );
308 for ( Tag responseParamTag : responseParamTags ) {
309
310 final WadlParamType wadlParam = new WadlParamType();
311 for ( Tag inlineTag : responseParamTag.inlineTags() ) {
312 final String tagName = inlineTag.name();
313 final String tagText = inlineTag.text();
314
315
316 if ( isEmpty( tagText ) ) {
317 if ( LOG.isLoggable( Level.FINE ) ) {
318 LOG.fine( "Skipping empty inline tag of @response.param in method " +
319 methodDoc.qualifiedName() + ": " + tagName );
320 }
321 continue;
322 }
323 if ( "@name".equals( tagName ) ) {
324 wadlParam.setName( tagText );
325 }
326 else if ( "@style".equals( tagName ) ) {
327 wadlParam.setStyle( tagText );
328 }
329 else if ( "@type".equals( tagName ) ) {
330 wadlParam.setType( QName.valueOf( tagText ) );
331 }
332 else if ( "@doc".equals( tagName ) ) {
333 wadlParam.setDoc( tagText );
334 }
335 else {
336 LOG.warning( "Unknown inline tag of @response.param in method " +
337 methodDoc.qualifiedName() + ": " + tagName +
338 " (value: " + tagText + ")" );
339 }
340 }
341 responseDoc.getWadlParams().add( wadlParam );
342 }
343
344 final Map<String, List<Tag>> tagsByStatus = getResponseRepresentationTags( methodDoc );
345 for ( Entry<String, List<Tag>> entry : tagsByStatus.entrySet() ) {
346 final RepresentationDocType representationDoc = new RepresentationDocType();
347 representationDoc.setStatus( Long.valueOf( entry.getKey() ) );
348 for ( Tag tag : entry.getValue() ) {
349 if ( tag.name().endsWith( ".qname" ) ) {
350 representationDoc.setElement( QName.valueOf( tag.text() ) );
351 }
352 else if ( tag.name().endsWith( ".mediaType" ) ) {
353 representationDoc.setMediaType( tag.text() );
354 }
355 else if ( tag.name().endsWith( ".example" ) ) {
356 representationDoc.setExample( getSerializedExample( tag, expand) );
357 }
358 else if ( tag.name().endsWith( ".doc" ) ) {
359 representationDoc.setDoc( tag.text() );
360 }
361 else {
362 LOG.warning( "Unknown response representation tag " + tag.name() );
363 }
364 }
365 responseDoc.getRepresentations().add( representationDoc );
366 }
367
368 methodDocType.setResponseDoc( responseDoc );
369 }
370
371 private static boolean isEmpty( String value ) {
372 return value == null || value.length() == 0 || value.trim().length() == 0 ? true : false;
373 }
374
375 private static void addRequestRepresentationDoc(MethodDoc methodDoc,
376 final MethodDocType methodDocType, Collection<String> expand) {
377 final Tag requestElement = getSingleTagOrNull( methodDoc, "request.representation.qname" );
378 final Tag requestExample = getSingleTagOrNull( methodDoc, "request.representation.example" );
379 if ( requestElement != null || requestExample != null ) {
380 final RequestDocType requestDoc = new RequestDocType();
381 final RepresentationDocType representationDoc = new RepresentationDocType();
382
383
384
385 if ( requestElement != null ) {
386 representationDoc.setElement( QName.valueOf( requestElement.text() ) );
387 }
388
389
390
391 if ( requestExample != null ) {
392 final String example = getSerializedExample( requestExample, expand);
393 if ( !isEmpty( example ) ) {
394 representationDoc.setExample( example );
395 }
396 else {
397 LOG.warning( "Could not get serialized example for method " + methodDoc.qualifiedName() );
398 }
399 }
400
401 requestDoc.setRepresentationDoc( representationDoc );
402 methodDocType.setRequestDoc( requestDoc );
403 }
404 }
405
406 private static Map<String, List<Tag>> getResponseRepresentationTags(
407 MethodDoc methodDoc ) {
408 final Map<String,List<Tag>> tagsByStatus = new HashMap<String, List<Tag>>();
409 for ( Tag tag : methodDoc.tags() ) {
410 final Matcher matcher = PATTERN_RESPONSE_REPRESENATION.matcher( tag.name() );
411 if ( matcher.matches() ) {
412 final String status = matcher.group( 1 );
413 List<Tag> tags = tagsByStatus.get( status );
414 if ( tags == null ) {
415 tags = new ArrayList<Tag>();
416 tagsByStatus.put( status, tags );
417 }
418 tags.add( tag );
419 }
420 }
421 return tagsByStatus;
422 }
423
424
425
426
427
428
429
430
431
432 private static String getSerializedExample(Tag tag, Collection<String> expand) {
433 if ( tag != null ) {
434 final Tag[] inlineTags = tag.inlineTags();
435 if ( inlineTags != null && inlineTags.length > 0 ) {
436 for ( Tag inlineTag : inlineTags ) {
437 if ( LOG.isLoggable( Level.FINE ) ) {
438 LOG.fine( "Have inline tag: " + print( inlineTag ) );
439 }
440 if ( "@link".equals( inlineTag.name() ) ) {
441 if ( LOG.isLoggable( Level.FINE ) ) {
442 LOG.fine( "Have link: " + print( inlineTag ) );
443 }
444 final SeeTag linkTag = (SeeTag) inlineTag;
445 return getSerializedLinkFromTag( linkTag, expand);
446 }
447 else if ( !isEmpty( inlineTag.text() ) ) {
448 return inlineTag.text();
449 }
450 }
451 }
452 else {
453 LOG.fine( "Have example: " + print( tag ) );
454 return tag.text();
455 }
456 }
457 return null;
458 }
459
460 private static Tag getSingleTagOrNull( MethodDoc methodDoc, String tagName ) {
461 final Tag[] tags = methodDoc.tags( tagName );
462 if ( tags != null && tags.length == 1 ) {
463 return tags[0];
464 }
465 return null;
466 }
467
468 private static void addParamDocs( MethodDoc methodDoc, final MethodDocType methodDocType, final DocProcessor docProcessor ) {
469
470 final Parameter[] parameters = methodDoc.parameters();
471 final ParamTag[] paramTags = methodDoc.paramTags();
472
473 if ( parameters != null && paramTags != null) {
474
475 Map<String, Parameter> params = new HashMap<String, Parameter>(parameters.length) {{
476 for (Parameter parameter : parameters)
477 {
478 put(parameter.name(), parameter);
479 }
480 }};
481
482 Map<String, ParamTag> tags = new HashMap<String, ParamTag>(parameters.length) {{
483 for (ParamTag paramTag : paramTags)
484 {
485 put(paramTag.parameterName(), paramTag);
486 }
487 }};
488
489 for (Entry<String, Parameter> parameterEntry : params.entrySet())
490 {
491 Parameter parameter = parameterEntry.getValue();
492 ParamTag paramTag = tags.get(parameterEntry.getKey());
493 if (paramTag != null) {
494
495 final ParamDocType paramDocType = new ParamDocType();
496 paramDocType.setParamName( paramTag.parameterName() );
497 paramDocType.setCommentText( paramTag.parameterComment() );
498 docProcessor.processParamTag( paramTag, parameter, paramDocType );
499
500 AnnotationDesc[] annotations = parameter.annotations();
501 if ( annotations != null ) {
502 for ( AnnotationDesc annotationDesc : annotations ) {
503 final AnnotationDocType annotationDocType = new AnnotationDocType();
504 final String typeName = annotationDesc.annotationType().qualifiedName();
505 annotationDocType.setAnnotationTypeName( typeName );
506 for ( ElementValuePair elementValuePair : annotationDesc.elementValues() ) {
507 final NamedValueType namedValueType = new NamedValueType();
508 namedValueType.setName( elementValuePair.element().name() );
509 namedValueType.setValue( elementValuePair.value().value().toString() );
510 annotationDocType.getAttributeDocs().add( namedValueType );
511 }
512 paramDocType.getAnnotationDocs().add( annotationDocType );
513 }
514 }
515
516 methodDocType.getParamDocs().add( paramDocType );
517 }
518 }
519 }
520 }
521
522 private static String getSerializedLinkFromTag(final SeeTag linkTag, Collection<String> expand) {
523 final MemberDoc referencedMember = linkTag.referencedMember();
524
525 if ( referencedMember == null ) {
526 LOG.warning("Referenced member of @link "+ print( linkTag ) +" cannot be resolved." );
527 return null;
528 }
529
530 if ( !referencedMember.isStatic() ) {
531 LOG.warning( "Referenced member of @link "+ print( linkTag ) +" is not static." +
532 " Right now only references to static members are supported." );
533 return null;
534 }
535
536
537
538 final ClassDoc containingClass = referencedMember.containingClass();
539 final Object object;
540 try {
541 Field declaredField = Class.forName( containingClass.qualifiedName(), false, Thread.currentThread().getContextClassLoader() ).getDeclaredField( referencedMember.name() );
542 if ( referencedMember.isFinal() ) {
543 declaredField.setAccessible( true );
544 }
545 object = declaredField.get( null );
546 LOG.log( Level.FINE, "Got object " + object );
547 } catch ( Exception e ) {
548 LOG.info( "Have classloader: " + ResourceDocletJSON.class.getClassLoader().getClass() );
549 LOG.info( "Have thread classloader " + Thread.currentThread().getContextClassLoader().getClass() );
550 LOG.info( "Have system classloader " + ClassLoader.getSystemClassLoader().getClass() );
551 LOG.log( Level.SEVERE, "Could not get field " + referencedMember.qualifiedName(), e );
552 return null;
553 }
554
555
556
557 try {
558 return marshallBean(object, expand);
559 } catch ( Exception e ) {
560 LOG.log( Level.SEVERE, "Could serialize bean to xml: " + object, e );
561 return null;
562 }
563 }
564
565 private static String marshallBean(Object object, Collection<String> expand) throws JAXBException, IOException
566 {
567
568 if (!expand.isEmpty()) {
569 new EntityCrawler().crawl(object, new DefaultExpandParameter(expand), getExpanders());
570 }
571
572 DefaultJaxbJsonMarshaller m = new DefaultJaxbJsonMarshaller(true);
573 final String result = m.marshal(object);
574 LOG.log(Level.FINE, "Got marshalled output:\n" + result);
575 return result;
576 }
577
578 private static String print( Tag tag ) {
579 final StringBuilder sb = new StringBuilder();
580 sb.append( tag.getClass() ).append( "[" );
581 sb.append( "firstSentenceTags=" ).append( toCSV( tag.firstSentenceTags() ) );
582 sb.append( ", inlineTags=" ).append( toCSV( tag.inlineTags() ) );
583 sb.append( ", kind=" ).append( tag.kind() );
584 sb.append( ", name=" ).append( tag.name() );
585 sb.append( ", text=" ).append( tag.text() );
586 sb.append( "]" );
587 return sb.toString();
588 }
589
590 static <T> String toCSV( Tag[] items ) {
591 if ( items == null ) {
592 return null;
593 }
594 return toCSV( Arrays.asList( items ) );
595 }
596
597 static <I> String toCSV( Collection<Tag> items ) {
598 return toCSV( items, ", ", null );
599 }
600
601 static <I> String toCSV( Collection<Tag> items, String separator, String delimiter ) {
602 if ( items == null ) {
603 return null;
604 }
605 if ( items.isEmpty() ) {
606 return "";
607 }
608 final StringBuilder sb = new StringBuilder();
609 for ( final Iterator<Tag> iter = items.iterator(); iter.hasNext(); ) {
610 if ( delimiter != null ) {
611 sb.append( delimiter );
612 }
613 final Tag item = iter.next();
614 sb.append( item.name() );
615 if ( delimiter != null ) {
616 sb.append( delimiter );
617 }
618 if ( iter.hasNext() ) {
619 sb.append( separator );
620 }
621 }
622 return sb.toString();
623 }
624
625
626
627
628
629
630
631
632 public static int optionLength( String option ) {
633 LOG.fine( "Invoked with option " + option );
634
635 if ( OPTION_OUTPUT.equals( option )
636 || OPTION_CLASSPATH.equals( option )
637 || OPTION_DOC_PROCESSORS.equals( option )
638 || OPTION_EXPAND.equals(option) ) {
639 return 2;
640 }
641
642 return 0;
643 }
644
645
646
647
648
649
650
651
652 public static boolean validOptions( String[][] options, DocErrorReporter reporter ) {
653 return validOption( OPTION_OUTPUT, "<path-to-file>", options, reporter )
654 && validOption( OPTION_CLASSPATH, "<path>", options, reporter );
655 }
656
657 private static boolean validOption( String optionName,
658 String reportOptionName,
659 String[][] options,
660 DocErrorReporter reporter ) {
661 final String option = getOptionArg( options, optionName );
662
663 final boolean foundOption = option != null && option.trim().length() > 0;
664 if ( !foundOption ) {
665 reporter.printError( optionName + " "+ reportOptionName +" must be specified." );
666 }
667 return foundOption;
668 }
669
670 private static String getOptionArg( String[][] options, String option ) {
671
672 for ( int i = 0; i < options.length; i++ ) {
673 String[] opt = options[i];
674
675 if ( opt[0].equals( option ) ) {
676 return opt[1];
677 }
678 }
679
680 return null;
681 }
682
683 static class Loader extends URLClassLoader {
684
685 public Loader(String[] paths, ClassLoader parent) {
686 super(getURLs(paths), parent);
687 }
688
689 Loader(String[] paths) {
690 super(getURLs(paths));
691 }
692
693 private static URL[] getURLs(String[] paths) {
694 final List<URL> urls = new ArrayList<URL>();
695 for (String path: paths) {
696 try {
697 urls.add(new File(path).toURI().toURL());
698 } catch (MalformedURLException e) {
699 throw new RuntimeException(e);
700 }
701 }
702 final URL[] us = urls.toArray(new URL[0]);
703 return us;
704 }
705
706 }
707
708 private static EntityExpanderResolver getExpanders()
709 {
710 return new ChainingEntityExpanderResolver(Lists.<EntityExpanderResolver>newArrayList(
711 new CollectionEntityExpanderResolver(),
712 new ListWrapperEntityExpanderResolver(),
713 new ExpandConstraintEntityExpanderResolver(),
714 new SelfExpandingExpander.Resolver(),
715 new IdentityEntityExpanderResolver()
716 ));
717 }
718 }