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 java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Arrays;
047: import java.util.HashSet;
048: import java.util.List;
049: import java.util.Set;
050: import java.util.logging.Level;
051: import java.util.logging.Logger;
052: import javax.lang.model.element.Modifier;
053: import org.netbeans.api.java.source.JavaSource;
054: import org.netbeans.api.java.source.ModificationResult;
055: import org.netbeans.api.java.source.WorkingCopy;
056: import org.netbeans.api.progress.ProgressHandle;
057: import org.netbeans.modules.websvc.rest.codegen.Constants.HttpMethodType;
058: import org.netbeans.modules.websvc.rest.codegen.Constants.MimeType;
059: import org.netbeans.modules.websvc.rest.codegen.model.GenericResourceBean;
060: import org.netbeans.modules.websvc.rest.codegen.model.ParameterInfo;
061: import org.netbeans.modules.websvc.rest.model.api.RestConstants;
062: import org.netbeans.modules.websvc.rest.support.AbstractTask;
063: import org.netbeans.modules.websvc.rest.support.JavaSourceHelper;
064: import org.openide.filesystems.FileObject;
065: import org.openide.util.NbBundle;
066:
067: /**
068: * Code generator for plain REST resource class.
069: * The generator takes as paramenters:
070: * - target directory
071: * - REST resource bean meta model.
072: *
073: * @author nam
074: */
075: public class GenericResourceGenerator extends AbstractGenerator {
076: public static final String RESOURCE_TEMPLATE = "Templates/WebServices/GenericResource.java"; //NOI18N
077: public static final String COMMENT_END_OF_GET = "TODO return proper representation object";
078:
079: private FileObject destDir;
080: private GenericResourceBean bean;
081: private String template;
082:
083: public GenericResourceGenerator(FileObject destDir,
084: GenericResourceBean bean) {
085: this .destDir = destDir;
086: this .bean = bean;
087: this .template = RESOURCE_TEMPLATE;
088: }
089:
090: public FileObject getDestDir() {
091: return destDir;
092: }
093:
094: public GenericResourceBean getBean() {
095: return bean;
096: }
097:
098: public String getTemplate() {
099: return template;
100: }
101:
102: public void setTemplate(String templatePath) {
103: template = templatePath;
104: }
105:
106: public Set<FileObject> generate(ProgressHandle pHandle)
107: throws IOException {
108: initProgressReporting(pHandle, false);
109:
110: reportProgress(NbBundle.getMessage(
111: GenericResourceGenerator.class, "MSG_GeneratingClass",
112: bean.getPackageName() + "." + bean.getName())); //NOI18N
113:
114: JavaSource source = JavaSourceHelper.createJavaSource(
115: getTemplate(), getDestDir(), bean.getPackageName(),
116: bean.getName());
117:
118: if (bean.getInputParameters().size() > 0) {
119: addInputParamFields(source);
120: addConstructorWithInputParams(source);
121: }
122:
123: modifyResourceClass(source);
124: return new HashSet<FileObject>(source.getFileObjects());
125: }
126:
127: private void addInputParamFields(JavaSource source)
128: throws IOException {
129: ModificationResult result = source
130: .runModificationTask(new AbstractTask<WorkingCopy>() {
131: public void run(WorkingCopy copy)
132: throws IOException {
133: copy
134: .toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
135: List<ParameterInfo> params = bean
136: .getInputParameters();
137:
138: JavaSourceHelper.addFields(copy,
139: getParamNames(params),
140: getParamTypeNames(params),
141: getParamValues(params));
142: }
143: });
144: result.commit();
145: }
146:
147: private void addConstructorWithInputParams(JavaSource source)
148: throws IOException {
149: ModificationResult result = source
150: .runModificationTask(new AbstractTask<WorkingCopy>() {
151: public void run(WorkingCopy copy)
152: throws IOException {
153: copy
154: .toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
155: ClassTree tree = JavaSourceHelper
156: .getTopLevelClassTree(copy);
157: List<ParameterInfo> params = bean
158: .getInputParameters();
159: String body = "{"; //NOI18N
160:
161: for (ParameterInfo param : params) {
162: String name = param.getName();
163: body += "if (" + name + " != null) {"
164: + "this." + name + " = " + name
165: + ";" + "}\n"; //NOI18N
166: }
167:
168: ClassTree modifiedTree = JavaSourceHelper
169: .addConstructor(copy, tree,
170: Constants.PUBLIC,
171: getParamNames(params),
172: getParamTypeNames(params),
173: body, null);
174:
175: copy.rewrite(tree, modifiedTree);
176: }
177: });
178: result.commit();
179: }
180:
181: private void modifyResourceClass(JavaSource source) {
182: try {
183: ModificationResult result = source
184: .runModificationTask(new AbstractTask<WorkingCopy>() {
185: public void run(WorkingCopy copy)
186: throws IOException {
187: copy.toPhase(JavaSource.Phase.RESOLVED);
188: JavaSourceHelper.addImports(copy,
189: getJsr311AnnotationImports(bean));
190: if (bean.isGenerateUriTemplate()) {
191: JavaSourceHelper
192: .addClassAnnotation(
193: copy,
194: new String[] { RestConstants.PATH_ANNOTATION },
195: new Object[] { bean
196: .getUriTemplate() });
197: }
198:
199: ClassTree initial = JavaSourceHelper
200: .getTopLevelClassTree(copy);
201: ClassTree tree = addMethods(copy, initial);
202:
203: for (GenericResourceBean subBean : bean
204: .getSubResources()) {
205: tree = addSubResourceLocatorMethod(
206: copy, tree, subBean);
207: }
208:
209: copy.rewrite(initial, tree);
210: }
211: });
212: result.commit();
213: } catch (IOException ex) {
214: Logger.getLogger(this .getClass().getName()).log(Level.INFO,
215: "", ex);
216: }
217: }
218:
219: public static String[] getJsr311AnnotationImports(
220: GenericResourceBean rbean) {
221: HashSet<String> result = new HashSet<String>();
222: if (rbean.isGenerateUriTemplate()) {
223: result.add(RestConstants.PATH);
224: }
225: if (rbean.getUriParams().length > 0) {
226: result.add(RestConstants.URI_PARAM);
227: }
228: for (HttpMethodType m : rbean.getMethodTypes()) {
229: result.add(m.getAnnotationType());
230: if (m == HttpMethodType.GET) {
231: result.add(RestConstants.PRODUCE_MIME);
232: }
233: if (m == HttpMethodType.POST || m == HttpMethodType.PUT) {
234: result.add(RestConstants.CONSUME_MIME);
235: }
236: }
237: if (rbean.getQueryParameters().size() > 0) {
238: result.add(RestConstants.QUERY_PARAM);
239: }
240: return result.toArray(new String[result.size()]);
241: }
242:
243: protected ClassTree addMethods(WorkingCopy copy, ClassTree tree) {
244: MimeType[] mimes = bean.getMimeTypes();
245: String[] types = bean.getRepresentationTypes();
246: for (int i = 0; i < mimes.length; i++) {
247: MimeType mime = mimes[i];
248: String type = types[i];
249: tree = addGetMethod(mime, type, copy, tree);
250:
251: if (bean.getMethodTypes().contains(HttpMethodType.POST)) {
252: tree = addPostMethod(mime, type, copy, tree);
253: }
254:
255: if (bean.getMethodTypes().contains(HttpMethodType.PUT)) {
256: tree = addPutMethod(mime, type, copy, tree);
257: }
258:
259: }
260:
261: if (bean.getMethodTypes().contains(HttpMethodType.DELETE)) {
262: tree = addDeleteMethod(copy, tree);
263: }
264: return tree;
265: }
266:
267: private ClassTree addGetMethod(MimeType mime, String type,
268: WorkingCopy copy, ClassTree tree) {
269: Modifier[] modifiers = Constants.PUBLIC;
270:
271: String[] annotations = new String[] {
272: RestConstants.GET_ANNOTATION,
273: RestConstants.PRODUCE_MIME_ANNOTATION };
274:
275: Object[] annotationAttrs = new Object[] { null, mime.value() };
276:
277: if (type == null) {
278: type = String.class.getName();
279: }
280: String bodyText = "{ //" + COMMENT_END_OF_GET + "\n";
281: bodyText += "throw new UnsupportedOperationException(); }";
282:
283: List<ParameterInfo> queryParams = bean.getQueryParameters();
284: String[] parameters = getGetParamNames(queryParams);
285: Object[] paramTypes = getGetParamTypes(queryParams);
286: String[][] paramAnnotations = getGetParamAnnotations(queryParams);
287: Object[][] paramAnnotationAttrs = getGetParamAnnotationAttrs(queryParams);
288:
289: String comment = "Retrieves representation of an instance of "
290: + bean.getQualifiedClassName() + "\n";
291: for (String param : parameters) {
292: comment += "@param $PARAM$ resource URI parameter\n"
293: .replace("$PARAM$", param);
294: }
295: comment += "@return an instance of " + type;
296:
297: return JavaSourceHelper.addMethod(copy, tree, modifiers,
298: annotations, annotationAttrs, getMethodName(
299: HttpMethodType.GET, mime), type, parameters,
300: paramTypes, paramAnnotations, paramAnnotationAttrs,
301: bodyText, comment); //NOI18N
302: }
303:
304: private ClassTree addPostMethod(MimeType mime, String type,
305: WorkingCopy copy, ClassTree tree) {
306: Modifier[] modifiers = Constants.PUBLIC;
307:
308: String[] annotations = new String[] {
309: RestConstants.POST_ANNOTATION,
310: RestConstants.CONSUME_MIME_ANNOTATION,
311: RestConstants.PRODUCE_MIME_ANNOTATION };
312:
313: Object[] annotationAttrs = new Object[] { null, mime.value(),
314: mime.value() };
315:
316: String bodyText = "{ //TODO\n return Response.created(context.getAbsolutePath()).build(); }"; //NOI18N
317: String[] parameters = getPostPutParams();
318: Object[] paramTypes = getPostPutParamTypes(type);
319: if (type != null) {
320: paramTypes[paramTypes.length - 1] = type;
321: }
322: String[] paramAnnotations = getParamAnnotations(parameters.length);
323: Object[] paramAnnotationAttrs = getParamAnnotationAttributes(parameters.length);
324:
325: String comment = "POST method for creating an instance of "
326: + bean.getName() + "\n";
327: for (int i = 0; i < parameters.length - 1; i++) {
328: comment += "@param $PARAM$ resource URI parameter\n"
329: .replace("$PARAM$", parameters[i]);
330: }
331: comment += "@param $PARAM$ representation for the new resource\n"
332: .replace("$PARAM$", parameters[parameters.length - 1]);
333: comment += "@return an HTTP response with content of the created resource";
334:
335: return JavaSourceHelper.addMethod(copy, tree, modifiers,
336: annotations, annotationAttrs, getMethodName(
337: HttpMethodType.POST, mime),
338: Constants.HTTP_RESPONSE, parameters, paramTypes,
339: paramAnnotations, paramAnnotationAttrs, bodyText,
340: comment);
341: }
342:
343: private ClassTree addPutMethod(MimeType mime, String type,
344: WorkingCopy copy, ClassTree tree) {
345: Modifier[] modifiers = Constants.PUBLIC;
346:
347: String[] annotations = new String[] {
348: RestConstants.PUT_ANNOTATION,
349: RestConstants.CONSUME_MIME_ANNOTATION };
350:
351: Object[] annotationAttrs = new Object[] { null, mime.value(),
352: mime.value() };
353: Object returnType = Constants.VOID;
354: String bodyText = "{ //TODO }"; //NOI18N
355:
356: String[] parameters = getPostPutParams();
357: Object[] paramTypes = getPostPutParamTypes(type);
358: if (type != null) {
359: paramTypes[paramTypes.length - 1] = type;
360: }
361: String[] paramAnnotations = getParamAnnotations(parameters.length);
362: Object[] paramAnnotationAttrs = getParamAnnotationAttributes(parameters.length);
363:
364: String comment = "PUT method for updating or creating an instance of "
365: + bean.getName() + "\n";
366: for (int i = 0; i < parameters.length - 1; i++) {
367: comment += "@param $PARAM$ resource URI parameter\n"
368: .replace("$PARAM$", parameters[i]);
369: }
370: comment += "@param $PARAM$ representation for the resource\n"
371: .replace("$PARAM$", parameters[parameters.length - 1]);
372: comment += "@return an HTTP response with content of the updated or created resource.";
373:
374: return JavaSourceHelper.addMethod(copy, tree, modifiers,
375: annotations, annotationAttrs, getMethodName(
376: HttpMethodType.PUT, mime), returnType,
377: parameters, paramTypes, paramAnnotations,
378: paramAnnotationAttrs, bodyText, comment);
379: }
380:
381: private ClassTree addDeleteMethod(WorkingCopy copy, ClassTree tree) {
382: Modifier[] modifiers = Constants.PUBLIC;
383:
384: String[] annotations = new String[] { RestConstants.DELETE_ANNOTATION, };
385:
386: Object[] annotationAttrs = new Object[] { null };
387:
388: Object returnType = Constants.VOID;
389: String bodyText = "{ //TODO implement }";
390:
391: String[] parameters = bean.getUriParams();
392: Object[] paramTypes = getUriParamTypes();
393: String[] paramAnnotations = getParamAnnotations(parameters.length);
394: Object[] paramAnnotationAttrs = getParamAnnotationAttributes(parameters.length);
395:
396: String comment = "DELETE method for resource " + bean.getName()
397: + "\n";
398: for (String param : parameters) {
399: comment += "@param $PARAM$ resource URI parameter\n"
400: .replace("$PARAM$", param);
401: }
402:
403: return JavaSourceHelper.addMethod(copy, tree, modifiers,
404: annotations, annotationAttrs, "delete", returnType,
405: parameters,
406: paramTypes, //NOI18N
407: paramAnnotations, paramAnnotationAttrs, bodyText,
408: comment); //NOI18N
409: }
410:
411: private ClassTree addSubResourceLocatorMethod(WorkingCopy copy,
412: ClassTree tree, GenericResourceBean subBean) {
413: Modifier[] modifiers = Constants.PUBLIC;
414: String methodName = "get" + subBean.getName(); //NOI18N
415:
416: String[] annotations = new String[] { RestConstants.PATH_ANNOTATION
417:
418: };
419:
420: Object[] annotationAttrs = new Object[] { subBean
421: .getUriTemplate() };
422: Object returnType = subBean.getName();
423:
424: String bodyText = "{ return new " + returnType + "(); }";
425:
426: String comment = "Sub-resource locator method for "
427: + subBean.getUriTemplate() + "\n";
428:
429: return JavaSourceHelper.addMethod(copy, tree, modifiers,
430: annotations, annotationAttrs, methodName, returnType,
431: null, null, null, null, bodyText, comment);
432: }
433:
434: public String getPostPutMethodBodyText(MimeType mimeType) {
435: return "{//TODO \n return new HttpResponse(201, new Representation(content, \""
436: + mimeType.value() + "\"); }"; //NOI18N
437: }
438:
439: private String[] getUriParamTypes() {
440: return getUriParamTypes(bean);
441: }
442:
443: public static String[] getUriParamTypes(GenericResourceBean bean) {
444: String defaultType = String.class.getName();
445: String[] types = new String[bean.getUriParams().length];
446: for (int i = 0; i < types.length; i++) {
447: types[i] = defaultType;
448: }
449: return types;
450: }
451:
452: private String[] getGetParamNames(List<ParameterInfo> queryParams) {
453: ArrayList<String> params = new ArrayList<String>();
454: params.addAll(Arrays.asList(bean.getUriParams()));
455: params.addAll(Arrays.asList(getParamNames(queryParams)));
456: return params.toArray(new String[params.size()]);
457: }
458:
459: private String[] getGetParamTypes(List<ParameterInfo> queryParams) {
460: ArrayList<String> types = new ArrayList<String>();
461: types.addAll(Arrays.asList(getUriParamTypes()));
462: types.addAll(Arrays.asList(getParamTypeNames(queryParams)));
463: return types.toArray(new String[types.size()]);
464: }
465:
466: private Object[] getParamAnnotationAttributes(int allParamCount) {
467: String[] uriParams = bean.getUriParams();
468: int uriParamCount = uriParams.length;
469: if (allParamCount < uriParamCount) {
470: throw new IllegalArgumentException("allParamCount="
471: + allParamCount);
472: }
473:
474: String[] attrs = new String[allParamCount];
475: for (int i = 0; i < uriParamCount; i++) {
476: attrs[i] = uriParams[i];
477: }
478: for (int i = uriParamCount; i < allParamCount; i++) {
479: attrs[i] = null;
480: }
481: return attrs;
482: }
483:
484: private String[] getParamAnnotations(int allParamCount) {
485: int uriParamCount = bean.getUriParams().length;
486: if (allParamCount < uriParamCount) {
487: throw new IllegalArgumentException("allParamCount="
488: + allParamCount);
489: }
490: String[] annos = new String[allParamCount];
491: for (int i = 0; i < uriParamCount; i++) {
492: annos[i] = RestConstants.URI_PARAM_ANNOTATION;
493: }
494: for (int i = uriParamCount; i < allParamCount; i++) {
495: annos[i] = null;
496: }
497: return annos;
498: }
499:
500: private String[][] getGetParamAnnotations(
501: List<ParameterInfo> queryParams) {
502: ArrayList<String[]> annos = new ArrayList<String[]>();
503:
504: for (String uriParam : bean.getUriParams()) {
505: annos
506: .add(new String[] { RestConstants.URI_PARAM_ANNOTATION });
507: }
508:
509: String[] annotations = null;
510: for (ParameterInfo param : queryParams) {
511: if (param.getDefaultValue() != null) {
512: annotations = new String[] {
513: RestConstants.QUERY_PARAM_ANNOTATION,
514: RestConstants.DEFAULT_VALUE_ANNOTATION };
515: } else {
516: annotations = new String[] { RestConstants.QUERY_PARAM_ANNOTATION };
517: }
518: annos.add(annotations);
519: }
520:
521: return annos.toArray(new String[annos.size()][]);
522: }
523:
524: private Object[][] getGetParamAnnotationAttrs(
525: List<ParameterInfo> queryParams) {
526: ArrayList<Object[]> attrs = new ArrayList<Object[]>();
527:
528: for (String uriParam : bean.getUriParams()) {
529: attrs.add(new Object[] { uriParam });
530: }
531:
532: Object[] annotationAttrs = null;
533: for (ParameterInfo param : queryParams) {
534: if (param.getDefaultValue() != null) {
535: annotationAttrs = new Object[] { param.getName(),
536: param.getDefaultValue().toString() };
537: } else {
538: annotationAttrs = new Object[] { param.getName() };
539: }
540: attrs.add(annotationAttrs);
541: }
542:
543: return attrs.toArray(new Object[attrs.size()][]);
544: }
545:
546: private String[] getPostPutParams() {
547: List<String> params = new ArrayList<String>(Arrays.asList(bean
548: .getUriParams()));
549: params.add("content"); //NO18N
550: return params.toArray(new String[params.size()]);
551: }
552:
553: private String[] getPostPutParamTypes(String representatinType) {
554: String defaultType = String.class.getName();
555: String[] types = new String[bean.getUriParams().length + 1];
556: for (int i = 0; i < types.length; i++) {
557: types[i] = defaultType;
558: }
559: types[types.length - 1] = representatinType;
560: return types;
561: }
562:
563: private String getMethodName(HttpMethodType methodType,
564: MimeType mime) {
565: return methodType.prefix() + mime.suffix();
566: }
567:
568: public static String getNounForMethodName(HttpMethodType type) {
569: String name = type.toString().toLowerCase();
570: StringBuilder sb = new StringBuilder(name);
571: sb.setCharAt(0, Character.toUpperCase(name.charAt(0)));
572: return sb.toString();
573: }
574:
575: private String[] getParamNames(List<ParameterInfo> params) {
576: List<String> results = new ArrayList<String>();
577:
578: for (ParameterInfo param : params) {
579: results.add(param.getName());
580: }
581:
582: return results.toArray(new String[results.size()]);
583: }
584:
585: private String[] getParamTypeNames(List<ParameterInfo> params) {
586: List<String> results = new ArrayList<String>();
587:
588: for (ParameterInfo param : params) {
589: results.add(param.getTypeName());
590: }
591:
592: return results.toArray(new String[results.size()]);
593: }
594:
595: private Object[] getParamValues(List<ParameterInfo> params) {
596: List<Object> results = new ArrayList<Object>();
597:
598: for (ParameterInfo param : params) {
599: Object defaultValue = null;
600:
601: if (!param.isQueryParam()) {
602: defaultValue = param.getDefaultValue();
603: }
604:
605: results.add(defaultValue);
606: }
607:
608: return results.toArray(new Object[results.size()]);
609: }
610:
611: }
|