001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.websvc.rest.codegen;
042:
043: import com.sun.source.tree.ClassTree;
044: import com.sun.source.tree.MethodTree;
045: import java.io.IOException;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Collection;
049: import java.util.HashSet;
050: import java.util.List;
051: import java.util.Set;
052: import javax.lang.model.element.AnnotationMirror;
053: import javax.lang.model.element.AnnotationValue;
054: import javax.lang.model.element.Element;
055: import javax.lang.model.element.ExecutableElement;
056: import javax.lang.model.element.TypeElement;
057: import javax.lang.model.element.VariableElement;
058: import javax.lang.model.type.DeclaredType;
059: import javax.lang.model.type.TypeKind;
060: import javax.lang.model.type.TypeMirror;
061: import javax.lang.model.util.ElementFilter;
062: import org.netbeans.api.java.source.JavaSource;
063: import org.netbeans.api.java.source.ModificationResult;
064: import org.netbeans.api.java.source.WorkingCopy;
065: import org.netbeans.api.progress.ProgressHandle;
066: import org.netbeans.api.project.FileOwnerQuery;
067: import org.netbeans.api.project.Project;
068: import org.netbeans.modules.websvc.rest.codegen.Constants.MimeType;
069: import org.netbeans.modules.websvc.rest.codegen.model.ParameterInfo;
070: import org.netbeans.modules.websvc.rest.codegen.model.RestComponentBean;
071: import org.netbeans.modules.websvc.rest.model.api.RestConstants;
072: import org.netbeans.modules.websvc.rest.support.AbstractTask;
073: import org.netbeans.modules.websvc.rest.support.Inflector;
074: import org.netbeans.modules.websvc.rest.support.JavaSourceHelper;
075: import org.netbeans.modules.websvc.rest.support.SourceGroupSupport;
076: import org.openide.filesystems.FileObject;
077: import static com.sun.source.tree.Tree.Kind.*;
078:
079: /**
080: * Code generator for REST services.
081: *
082: * @author nam
083: */
084: public abstract class RestComponentGenerator extends AbstractGenerator {
085:
086: private static final String COMMENT_END_OF_HTTP_MEHTOD_GET = "TODO return proper representation object";
087: private static final String GENERIC_REF_CONVERTER_TEMPLATE = "Templates/WebServices/RefConverter.java"; //NOI18N
088: private static final String GENERIC_REF_CONVERTER = "GenericRefConverter"; //NOI18N
089: protected FileObject targetFile; // resource file target of the drop
090: protected FileObject destDir;
091: protected FileObject wrapperResourceFile;
092: protected Project project;
093: protected RestComponentBean bean;
094: protected JavaSource wrapperResourceJS;
095: protected JavaSource targetResourceJS;
096: protected JavaSource jaxbOutputWrapperJS;
097: protected String subresourceLocatorName;
098: protected String subresourceLocatorUriTemplate;
099: private Collection<String> existingUriTemplates;
100:
101: public RestComponentGenerator(FileObject targetFile,
102: RestComponentBean bean) {
103: this .targetFile = targetFile;
104: this .destDir = targetFile.getParent();
105: project = FileOwnerQuery.getOwner(targetFile);
106:
107: if (project == null) {
108: throw new IllegalArgumentException(targetFile.getPath()
109: + " is not part of a project.");
110: }
111:
112: targetResourceJS = JavaSource.forFileObject(targetFile);
113: String packageName = JavaSourceHelper
114: .getPackageName(targetResourceJS);
115: bean.setPackageName(packageName);
116: bean.setPrivateFieldForQueryParam(true);
117: this .bean = bean;
118: wrapperResourceFile = SourceGroupSupport.findJavaSourceFile(
119: project, bean.getName());
120: }
121:
122: public RestComponentBean getBean() {
123: return bean;
124: }
125:
126: public boolean wrapperResourceExists() {
127: return wrapperResourceFile != null;
128: }
129:
130: public Set<FileObject> generate(ProgressHandle pHandle)
131: throws IOException {
132: initProgressReporting(pHandle);
133:
134: preGenerate();
135:
136: FileObject outputWrapperFO = generateJaxbOutputWrapper();
137: if (outputWrapperFO != null) {
138: jaxbOutputWrapperJS = JavaSource
139: .forFileObject(outputWrapperFO);
140: }
141: generateComponentResourceClass();
142: addSubresourceLocator();
143: FileObject refConverterFO = getOrCreateGenericRefConverter()
144: .getFileObjects().iterator().next();
145: modifyTargetConverter();
146: FileObject[] result = new FileObject[] { targetFile,
147: wrapperResourceFile, refConverterFO, outputWrapperFO };
148: if (outputWrapperFO == null) {
149: result = new FileObject[] { targetFile,
150: wrapperResourceFile, refConverterFO };
151: }
152: JavaSourceHelper.saveSource(result);
153:
154: postGenerate();
155:
156: finishProgressReporting();
157:
158: return new HashSet<FileObject>(Arrays.asList(result));
159: }
160:
161: protected void preGenerate() throws IOException {
162: }
163:
164: protected void postGenerate() throws IOException {
165: }
166:
167: protected abstract String getCustomMethodBody() throws IOException;
168:
169: protected FileObject generateJaxbOutputWrapper() throws IOException {
170: MimeType mimeType = bean.getMimeTypes()[0];
171:
172: if (mimeType == MimeType.JSON || mimeType == MimeType.XML) {
173: FileObject converterFolder = getConverterFolder();
174: String packageName = SourceGroupSupport
175: .packageForFolder(converterFolder);
176: bean.setOutputWrapperPackageName(packageName);
177: String[] returnTypeNames = bean.getOutputTypes();
178: XmlOutputWrapperGenerator gen = new XmlOutputWrapperGenerator(
179: converterFolder, bean.getOutputWrapperName(),
180: packageName, returnTypeNames);
181:
182: return gen.generate();
183: }
184:
185: return null;
186: }
187:
188: protected void generateComponentResourceClass() throws IOException {
189: if (wrapperResourceFile == null) {
190: GenericResourceGenerator delegate = new GenericResourceGenerator(
191: destDir, bean);
192: delegate.setTemplate(bean.getResourceClassTemplate());
193: Set<FileObject> files = delegate
194: .generate(getProgressHandle());
195:
196: if (files == null || files.size() == 0) {
197: return;
198: }
199: wrapperResourceFile = files.iterator().next();
200: wrapperResourceJS = JavaSource
201: .forFileObject(wrapperResourceFile);
202: addSupportingMethods();
203: modifyGetMethod();
204: } else {
205: wrapperResourceJS = JavaSource
206: .forFileObject(wrapperResourceFile);
207: }
208: }
209:
210: protected void addSubresourceLocator() throws IOException {
211: ModificationResult result = targetResourceJS
212: .runModificationTask(new AbstractTask<WorkingCopy>() {
213:
214: public void run(WorkingCopy copy)
215: throws IOException {
216: copy.toPhase(JavaSource.Phase.RESOLVED);
217: JavaSourceHelper.addImports(copy,
218: getSubresourceLocatorImports());
219:
220: ClassTree tree = JavaSourceHelper
221: .getTopLevelClassTree(copy);
222: String[] annotations = new String[] { RestConstants.PATH_ANNOTATION };
223: Object[] annotationAttrs = new Object[] { getSubresourceLocatorUriTemplate() };
224: boolean addTryFinallyBlock = false;
225:
226: if (hasGetEntityMethod(JavaSourceHelper
227: .getTopLevelClassElement(copy))) {
228: addTryFinallyBlock = true;
229: }
230:
231: String body = "{";
232:
233: if (addTryFinallyBlock) {
234: body += "try {";
235: }
236:
237: body += getParamInitStatements(copy);
238: body += "return new $CLASS$($ARGS$);}";
239: body = body.replace("$CLASS$", JavaSourceHelper
240: .getClassName(wrapperResourceJS));
241: body = body.replace("$ARGS$", getParamList());
242:
243: String comment = "Returns " + bean.getName()
244: + " sub-resource.\n";
245:
246: if (addTryFinallyBlock) {
247: body += "finally { PersistenceService.getInstance().close()";
248: }
249:
250: ClassTree modifiedTree = JavaSourceHelper
251: .addMethod(copy, tree,
252: Constants.PUBLIC, annotations,
253: annotationAttrs,
254: getSubresourceLocatorName(),
255: bean.getQualifiedClassName(),
256: null, null, null, null, body,
257: comment);
258: copy.rewrite(tree, modifiedTree);
259: }
260: });
261: result.commit();
262: }
263:
264: /**
265: * Add supporting methods, if any, for GET
266: */
267: protected void addSupportingMethods() throws IOException {
268: }
269:
270: /**
271: * Return target and generated file objects
272: */
273: protected void modifyGetMethod() throws IOException {
274: ModificationResult result = wrapperResourceJS
275: .runModificationTask(new AbstractTask<WorkingCopy>() {
276:
277: public void run(WorkingCopy copy)
278: throws IOException {
279: copy
280: .toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
281: String converterType = getConverterType();
282: if (converterType != null) {
283: JavaSourceHelper
284: .addImports(
285: copy,
286: new String[] { getConverterType() });
287: }
288:
289: String methodBody = "{"
290: + getOverridingStatements(); //NOI18N
291: methodBody += getCustomMethodBody();
292:
293: methodBody += "}"; //NOI18N
294: for (MimeType mime : bean.getMimeTypes()) {
295: MethodTree methodTree = JavaSourceHelper
296: .getMethodByName(copy, bean
297: .getGetMethodName(mime));
298: JavaSourceHelper.replaceMethodBody(copy,
299: methodTree, methodBody);
300: }
301: }
302: });
303: result.commit();
304: }
305:
306: protected String getOverridingStatements() {
307: String text = ""; //NOI18N
308: for (ParameterInfo param : bean.getQueryParameters()) {
309: String name = param.getName();
310: text += "if (this." + name + " != null) {" + name
311: + " = this." + name + ";" + "}\n";
312: }
313:
314: return text;
315: }
316:
317: protected JavaSource getOrCreateGenericRefConverter() {
318: FileObject converterFolder = getConverterFolder();
319: String packageName = SourceGroupSupport
320: .packageForFolder(converterFolder);
321: FileObject refConverterFO = converterFolder.getFileObject(
322: GENERIC_REF_CONVERTER, "java"); //NOI18N
323: if (refConverterFO == null) {
324: JavaSource source = JavaSourceHelper.createJavaSource(
325: GENERIC_REF_CONVERTER_TEMPLATE, converterFolder,
326: packageName, GENERIC_REF_CONVERTER);
327: return source;
328: } else {
329: return JavaSource.forFileObject(refConverterFO);
330: }
331: }
332:
333: protected String getConverterType() throws IOException {
334: if (jaxbOutputWrapperJS != null) {
335: return JavaSourceHelper.getClassType(jaxbOutputWrapperJS);
336: }
337: return null;
338: }
339:
340: protected String getConverterName() throws IOException {
341: String converterType = getConverterType();
342: if (converterType == null) {
343: return null;
344: }
345: return converterType
346: .substring(converterType.lastIndexOf('.') + 1);
347: }
348:
349: private String getParamInitStatements(WorkingCopy copy) {
350: String comment = "// TODO: Assign a value to one of the following variables if you want to \n"
351: + "// override the corresponding default value or value from the query \n"
352: + "// parameter in the subresource class.\n"; //NOI18N
353: String statements = ""; //NOI18N
354: boolean addGetEntityStatement = false;
355:
356: for (ParameterInfo param : bean.getInputParameters()) {
357: String initValue = "null"; //NOI18N
358: String access = match(JavaSourceHelper
359: .getTopLevelClassElement(copy), param.getName());
360:
361: if (access != null) {
362: initValue = access;
363: addGetEntityStatement = true;
364: }
365:
366: statements += param.getSimpleTypeName() + " "
367: + param.getName() + " = " + initValue + ";"; //NOI18N
368: }
369:
370: String getEntityStatement = "";
371:
372: if (addGetEntityStatement) {
373: getEntityStatement = getEntityType(JavaSourceHelper
374: .getTopLevelClassElement(copy))
375: + " entity = getEntity()";
376: }
377:
378: return comment + getEntityStatement + statements;
379: }
380:
381: private String getParamList() {
382: List<ParameterInfo> inputParams = bean.getInputParameters();
383: String text = ""; //NOI18N
384: for (int i = 0; i < inputParams.size(); i++) {
385: ParameterInfo param = inputParams.get(i);
386:
387: if (i == 0) {
388: text += param.getName();
389: } else {
390: text += ", " + param.getName(); //NOI18N
391: }
392: }
393:
394: return text;
395: }
396:
397: private String getOutputWrapperQualifiedName() throws IOException {
398: return JavaSourceHelper.getClassType(jaxbOutputWrapperJS);
399: }
400:
401: private boolean hasGetEntityMethod(TypeElement typeElement) {
402: List<ExecutableElement> methods = ElementFilter
403: .methodsIn(typeElement.getEnclosedElements());
404:
405: for (ExecutableElement method : methods) {
406: if (method.getSimpleName().contentEquals("getEntity")) {
407: return true;
408: }
409: }
410:
411: return false;
412: }
413:
414: private String getUriParam(TypeElement typeElement) {
415: List<ExecutableElement> methods = ElementFilter
416: .methodsIn(typeElement.getEnclosedElements());
417: boolean hasMethodGetEntity = false;
418: String uriParam = null;
419: for (ExecutableElement method : methods) {
420: if (hasMethodGetEntity && uriParam != null) {
421: return uriParam;
422: }
423: if (method.getSimpleName().contentEquals("getEntity")) {
424: hasMethodGetEntity = true;
425: }
426: for (VariableElement ve : method.getParameters()) {
427: List<? extends AnnotationMirror> annotations = ve
428: .getAnnotationMirrors();
429: for (AnnotationMirror m : annotations) {
430: if (JavaSourceHelper.isOfAnnotationType(m,
431: RestConstants.URI_PARAM_ANNOTATION)) {
432: Collection<? extends AnnotationValue> values = m
433: .getElementValues().values();
434: for (AnnotationValue av : values) {
435: if (av.getValue() instanceof String) {
436: String v = (String) av.getValue();
437: uriParam = v;
438: }
439: }
440: }
441: }
442: }
443: }
444: return null;
445: }
446:
447: private String getEntityType(TypeElement typeElement) {
448: List<ExecutableElement> methods = ElementFilter
449: .methodsIn(typeElement.getEnclosedElements());
450:
451: for (ExecutableElement method : methods) {
452: if (method.getSimpleName().contentEquals("getEntity")) {
453: return method.getReturnType().toString();
454: }
455: }
456: return null;
457: }
458:
459: public String getSubresourceLocatorName() {
460: if (subresourceLocatorName == null) {
461: String uriTemplate = getSubresourceLocatorUriTemplate();
462:
463: if (uriTemplate.endsWith("/")) {
464: uriTemplate = uriTemplate.substring(0, uriTemplate
465: .length() - 1);
466: }
467: subresourceLocatorName = "get"
468: + Inflector.getInstance().camelize(uriTemplate);
469: }
470:
471: return subresourceLocatorName;
472: }
473:
474: public void setSubresourceLocatorName(String name) {
475: this .subresourceLocatorName = name;
476: }
477:
478: public String getSubresourceLocatorUriTemplate() {
479: if (subresourceLocatorUriTemplate == null) {
480: subresourceLocatorUriTemplate = getAvailableUriTemplate();
481: }
482:
483: if (!subresourceLocatorUriTemplate.endsWith("/")) {
484: //NOI18N
485: subresourceLocatorUriTemplate += "/"; //NOI18N
486: }
487: return subresourceLocatorUriTemplate;
488: }
489:
490: public void setSubresourceLocatorUriTemplate(String uriTemplate) {
491: this .subresourceLocatorUriTemplate = uriTemplate;
492: }
493:
494: //
495: // public List shrink(Object[] array) {
496: // ArrayList result = new ArrayList();
497: // for (Object o : array) {
498: // if (o != null) {
499: // result.add(o);
500: // }
501: // }
502: // return result;
503: // }
504: //
505: // public List<String> shrink(String[] array) {
506: // ArrayList<String> result = new ArrayList<String>();
507: // for (String o : array) {
508: // if (o != null) {
509: // result.add(o);
510: // }
511: // }
512: // return result;
513: // }
514: //
515: // public String[] getQueryParamAnnotations(GenericResourceBean bean) {
516: // String[] anns = new String[bean.getQueryParams().length];
517: // for (int i = 0; i < anns.length; i++) {
518: // anns[i] = Constants.QUERY_PARAM_ANNOTATION;
519: // }
520: // return anns;
521: // }
522: //
523: private String match(TypeElement targetClass, String arg) {
524: //List<VariableElement> fields = ElementFilter.fieldsIn(targetClass.getEnclosedElements());
525: String argName = arg.toLowerCase();
526: // for (VariableElement field : fields) {
527: // if (match(field.getSimpleName().toString(), argName)) {
528: // return field.getSimpleName().toString();
529: // }
530: // }
531:
532: List<ExecutableElement> methods = ElementFilter
533: .methodsIn(targetClass.getEnclosedElements());
534: for (ExecutableElement method : methods) {
535: if ("getEntity".equals(method.getSimpleName().toString())) {
536: TypeMirror tm = method.getReturnType();
537: if (tm.getKind() == TypeKind.DECLARED) {
538: Element element = ((DeclaredType) tm).asElement();
539: List<ExecutableElement> eMethods = ElementFilter
540: .methodsIn(element.getEnclosedElements());
541: for (ExecutableElement m : eMethods) {
542: String mName = m.getSimpleName().toString()
543: .toLowerCase();
544: if (mName.startsWith("get")
545: && match(mName.substring(3), argName)) {
546: return "entity."
547: + m.getSimpleName().toString()
548: + "()"; //NOI18N
549: }
550: }
551: }
552: }
553: }
554: return null;
555: }
556:
557: private boolean match(String s1, String lower) {
558: String s10 = s1.toLowerCase();
559: return s10.indexOf(lower) > -1 || lower.indexOf(s10) > -1;
560: }
561:
562: private void modifyTargetConverter() throws IOException {
563: TypeElement targetResourceType = JavaSourceHelper
564: .getTypeElement(targetResourceJS);
565: //System.out.println("targetResourceJS = " + targetResourceJS);
566: //System.out.println("targetResourceType = " + targetResourceType);
567: TypeElement representationType = JavaSourceHelper
568: .getXmlRepresentationClass(targetResourceType,
569: EntityResourcesGenerator.CONVERTER_SUFFIX);
570: //System.out.println("representationType = " + representationType);
571: if (representationType != null) {
572: JavaSource representationJS = JavaSourceHelper
573: .forTypeElement(representationType, project);
574: ModificationResult result = representationJS
575: .runModificationTask(new AbstractTask<WorkingCopy>() {
576:
577: public void run(WorkingCopy copy)
578: throws IOException {
579: copy.toPhase(JavaSource.Phase.RESOLVED);
580: ClassTree tree = JavaSourceHelper
581: .getTopLevelClassTree(copy);
582: ClassTree modifiedTree = addGetComponentRefMethod(
583: copy, tree);
584: copy.rewrite(tree, modifiedTree);
585: }
586: });
587: result.commit();
588: }
589: }
590:
591: private ClassTree addGetComponentRefMethod(WorkingCopy copy,
592: ClassTree tree) {
593: String[] annotations = new String[] { Constants.XML_ELEMENT_ANNOTATION };
594: String uriTemplate = getSubresourceLocatorUriTemplate();
595: String xmlElementName = null;
596:
597: if (uriTemplate.endsWith("/")) {
598: xmlElementName = uriTemplate.substring(0, uriTemplate
599: .length() - 1)
600: + "Ref";
601: } else {
602: xmlElementName = uriTemplate + "Ref";
603: }
604:
605: Object[] annotationAttrs = new Object[] { JavaSourceHelper
606: .createAssignmentTree(copy, "name", xmlElementName) };
607:
608: String body = "{ return new $CLASS$(uri.resolve(\"$URITEMPLATE$\")); }";
609: body = body.replace("$CLASS$", GENERIC_REF_CONVERTER);
610: body = body.replace("$URITEMPLATE$", uriTemplate);
611: String comment = "Returns reference to " + bean.getName()
612: + " resource.\n";
613: String methodName = getSubresourceLocatorName() + "Ref";
614: return JavaSourceHelper.addMethod(copy, tree, Constants.PUBLIC,
615: annotations, annotationAttrs, methodName,
616: GENERIC_REF_CONVERTER, null, null, null, null, body,
617: comment);
618: }
619:
620: private FileObject getConverterFolder() {
621: FileObject converterDir = destDir;
622: if (destDir.getParent() != null) {
623: FileObject dir = destDir.getParent().getFileObject(
624: EntityResourcesGenerator.CONVERTER_FOLDER);
625: if (dir != null) {
626: converterDir = dir;
627: }
628: }
629: return converterDir;
630: }
631:
632: public Collection<String> getExistingUriTemplates() {
633: if (existingUriTemplates == null) {
634: existingUriTemplates = JavaSourceHelper
635: .getAnnotationValuesForAllMethods(targetResourceJS,
636: RestConstants.PATH);
637: }
638:
639: return existingUriTemplates;
640: }
641:
642: private String getAvailableUriTemplate() {
643: //TODO: Need to create an unique UriTemplate value.
644: Collection<String> existingUriTemplates = getExistingUriTemplates();
645: int counter = 1;
646: String uriTemplate = Inflector.getInstance().camelize(
647: bean.getShortName(), true);
648: String temp = uriTemplate;
649:
650: while (existingUriTemplates.contains(temp)
651: || existingUriTemplates.contains(temp + "/")) {
652: //NOI18N
653: temp = uriTemplate + counter++;
654: }
655:
656: return temp;
657: }
658:
659: private String[] getSubresourceLocatorImports() throws IOException {
660: List<String> imports = new ArrayList<String>();
661: //imports.add(getOutputWrapperQualifiedName());
662: List<ParameterInfo> inputParams = bean.getInputParameters();
663:
664: for (ParameterInfo param : inputParams) {
665: if (!param.getType().getPackage().getName().equals(
666: "java.lang")) {
667: imports.add(param.getType().getName());
668: }
669: }
670:
671: return imports.toArray(new String[imports.size()]);
672: }
673: }
|