| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.websvc.rest.editor; |
| |
| import java.awt.Toolkit; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| |
| import org.netbeans.api.j2ee.core.Profile; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.ModificationResult; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.openide.awt.StatusDisplayer; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.NbBundle; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelException; |
| import org.netbeans.modules.web.api.webmodule.WebModule; |
| import org.netbeans.modules.websvc.rest.RestUtils; |
| import org.netbeans.modules.websvc.rest.model.api.RestMethodDescription; |
| import org.netbeans.modules.websvc.rest.model.api.RestServiceDescription; |
| import org.netbeans.modules.websvc.rest.model.api.RestServices; |
| import org.netbeans.modules.websvc.rest.model.api.RestServicesMetadata; |
| import org.netbeans.modules.websvc.rest.model.api.RestServicesModel; |
| |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ModifiersTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.TypeParameterTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| |
| |
| /** |
| * @author ads |
| * |
| */ |
| abstract class AsyncConverter { |
| |
| boolean isApplicable(FileObject fileObject){ |
| Project project = FileOwnerQuery.getOwner(fileObject); |
| if (project == null) { |
| return false; |
| } |
| WebModule webModule = WebModule.getWebModule(project |
| .getProjectDirectory()); |
| if (webModule == null) { |
| return false; |
| } |
| Profile profile = webModule.getJ2eeProfile(); |
| if (!Profile.JAVA_EE_7_WEB.equals(profile) |
| && !Profile.JAVA_EE_7_FULL.equals( profile) |
| && !Profile.JAVA_EE_8_WEB.equals(profile) |
| && !Profile.JAVA_EE_8_FULL.equals(profile) |
| && !Profile.JAKARTA_EE_8_WEB.equals(profile) |
| && !Profile.JAKARTA_EE_8_FULL.equals(profile) |
| && !Profile.JAKARTA_EE_9_WEB.equals(profile) |
| && !Profile.JAKARTA_EE_9_FULL.equals(profile) |
| && !Profile.JAKARTA_EE_9_1_WEB.equals(profile) |
| && !Profile.JAKARTA_EE_9_1_FULL.equals(profile)) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| boolean isApplicable(Element element){ |
| if ( element == null || element.getKind() != ElementKind.METHOD){ |
| return false; |
| } |
| |
| Element enclosingElement = element.getEnclosingElement(); |
| if (!(enclosingElement instanceof TypeElement)){ |
| return false; |
| } |
| return true; |
| } |
| |
| protected abstract Logger getLogger(); |
| |
| protected boolean isAsync(Element method){ |
| if ( method instanceof ExecutableElement ){ |
| ExecutableElement exec = (ExecutableElement)method; |
| List<? extends VariableElement> parameters = exec.getParameters(); |
| for (VariableElement param : parameters) { |
| boolean hasAsyncType = false; |
| TypeMirror type = param.asType(); |
| if ( type instanceof DeclaredType ){ |
| Element paramElement = ((DeclaredType)type).asElement(); |
| if ( paramElement instanceof TypeElement ){ |
| hasAsyncType = ((TypeElement)paramElement).getQualifiedName(). |
| contentEquals("javax.ws.rs.container.AsyncResponse"); // NOI18N |
| } |
| } |
| if( hasAsyncType && hasAnnotation(param, |
| "javax.ws.rs.container.Suspended")){ // NOI18N |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected boolean checkRestMethod( final String fqn , Element method, |
| FileObject source) { |
| final String methodName = method.getSimpleName().toString(); |
| /* |
| * TODO : method name doesn't uniquely identify the method. |
| * So implementation should be improved |
| */ |
| Project project = FileOwnerQuery.getOwner(source); |
| RestServicesModel model = RestUtils.getRestServicesMetadataModel(project); |
| if ( model == null){ |
| return false; |
| } |
| try { |
| return model.runReadAction( |
| new MetadataModelAction<RestServicesMetadata, Boolean>() |
| { |
| |
| @Override |
| public Boolean run( RestServicesMetadata metadata ) |
| throws Exception |
| { |
| RestServices services = metadata.getRoot(); |
| RestServiceDescription[] descriptions = |
| services.getRestServiceDescription(); |
| for (RestServiceDescription description : descriptions) { |
| if ( fqn.equals(description.getClassName())){ |
| List<RestMethodDescription> methods = |
| description.getMethods(); |
| for (RestMethodDescription method : methods){ |
| if ( methodName.equals(method.getName())){ |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| }); |
| } |
| catch(MetadataModelException e ){ |
| getLogger().log(Level.INFO, null, e); |
| } |
| catch(IOException e ){ |
| getLogger().log(Level.INFO, null, e); |
| } |
| return false; |
| } |
| |
| protected boolean hasAnnotation(Element element, String... annotationFqns){ |
| List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors(); |
| for (AnnotationMirror annotation : annotations) { |
| Element annotationElement = annotation.getAnnotationType().asElement(); |
| if ( annotationElement instanceof TypeElement){ |
| String fqn = ((TypeElement)annotationElement).getQualifiedName(). |
| toString(); |
| for(String annotationFqn : annotationFqns){ |
| if (fqn.equals(annotationFqn)){ |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected void convertMethod( final ElementHandle<Element> handle, |
| FileObject fileObject ) throws IOException |
| { |
| JavaSource javaSource = JavaSource.forFileObject(fileObject); |
| if (javaSource == null) { |
| return; |
| } |
| ModificationResult task = javaSource |
| .runModificationTask(new Task<WorkingCopy>() { |
| |
| @Override |
| public void run( WorkingCopy copy ) throws Exception { |
| copy.toPhase(Phase.ELEMENTS_RESOLVED); |
| |
| Element restMethod = handle.resolve(copy); |
| if (restMethod == null) { |
| return; |
| } |
| Element enclosingElement = restMethod |
| .getEnclosingElement(); |
| if (!(enclosingElement instanceof TypeElement)) { |
| return; |
| } |
| ClassTree classTree = (ClassTree) copy.getTrees() |
| .getTree(enclosingElement); |
| |
| MethodTree method = (MethodTree) copy.getTrees() |
| .getTree(restMethod); |
| String name = restMethod.getSimpleName().toString(); |
| String asyncName = findFreeName(name, enclosingElement, |
| restMethod); |
| String movedName = findFreeName( |
| convertMethodName(name), enclosingElement, |
| restMethod); |
| |
| TreeMaker maker = copy.getTreeMaker(); |
| boolean isEjb = isEjb(enclosingElement); |
| ClassTree newTree = classTree; |
| if (!isEjb) { |
| newTree = addExecutionService(maker, copy, |
| enclosingElement, classTree); |
| } |
| newTree = createAsyncMethod(maker, asyncName, |
| serviceField, method, movedName, copy, newTree, |
| isEjb); |
| newTree = moveRestMethod(maker, movedName, method, |
| copy, newTree); |
| copy.rewrite(classTree, newTree); |
| } |
| |
| private ClassTree addExecutionService( TreeMaker maker, |
| WorkingCopy copy, Element clazz, ClassTree classTree ) |
| { |
| List<VariableElement> fields = ElementFilter |
| .fieldsIn(clazz.getEnclosedElements()); |
| Set<String> fieldNames = new HashSet<String>(); |
| for (VariableElement field : fields) { |
| fieldNames.add(field.getSimpleName().toString()); |
| TypeMirror fieldType = field.asType(); |
| Element fieldTypeElement = copy.getTypes() |
| .asElement(fieldType); |
| if (fieldTypeElement instanceof TypeElement) { |
| TypeElement type = (TypeElement) fieldTypeElement; |
| if (ExecutorService.class.getName() |
| .contentEquals(type.getQualifiedName())) |
| { |
| serviceField = field.getSimpleName() |
| .toString(); |
| } |
| } |
| } |
| if (serviceField == null) { |
| String name = "executorService"; // NOI18N |
| serviceField = name; |
| int i = 0; |
| while (fieldNames.contains(serviceField)) { |
| serviceField = name + i; |
| i++; |
| } |
| } |
| else { |
| return classTree; |
| } |
| MethodInvocationTree init = maker.MethodInvocation( |
| Collections.<ExpressionTree> emptyList(), |
| maker.QualIdent(Executors.class.getName() |
| + ".newCachedThreadPool"), |
| Collections.<ExpressionTree> emptyList()); |
| VariableTree service = maker.Variable(maker |
| .Modifiers(EnumSet.of(Modifier.PRIVATE)), |
| serviceField, maker |
| .QualIdent(ExecutorService.class |
| .getName()), init); |
| return maker.addClassMember(classTree, service); |
| } |
| |
| private String serviceField; |
| |
| }); |
| task.commit(); |
| } |
| |
| private String findFreeName( String name,Element enclosingElement, |
| Element havingName) |
| { |
| for(ExecutableElement method: |
| ElementFilter.methodsIn(enclosingElement.getEnclosedElements())) |
| { |
| if (method.equals(havingName)){ |
| continue; |
| } |
| if ( method.getSimpleName().contentEquals(name)){ |
| return findFreeName(name+1,enclosingElement, havingName); |
| } |
| } |
| return name; |
| } |
| |
| private String convertMethodName(String name){ |
| if ( name.length()<=1){ |
| return "do"+name; // NOI18N |
| } |
| else { |
| return "do"+Character.toUpperCase(name.charAt(0))+name.substring(1); |
| } |
| } |
| |
| private boolean isEjb(Element element){ |
| return hasAnnotation(element, "javax.ejb.Stateless", |
| "javax.ejb.Singleton");// NOI18N |
| } |
| |
| private ClassTree createAsyncMethod( TreeMaker maker, |
| String asyncName, String service, MethodTree method, String movedName , |
| WorkingCopy copy, ClassTree classTree, boolean isEjb) |
| { |
| ModifiersTree modifiers = method.getModifiers(); |
| if ( isEjb ){ |
| AnnotationTree async = maker.Annotation(maker.QualIdent( |
| "javax.ejb.Asynchronous"), // NOI18N |
| Collections.<ExpressionTree>emptyList()); |
| modifiers = maker.addModifiersAnnotation(modifiers, async); |
| } |
| List<? extends VariableTree> parameters = method.getParameters(); |
| String asyncReponseParam = getAsynParam("asyncResponse",parameters);//NOI18N |
| |
| ModifiersTree paramModifier = maker.Modifiers(EnumSet.of(Modifier.FINAL)); |
| AnnotationTree annotation = maker.Annotation( |
| maker.QualIdent("javax.ws.rs.container.Suspended"), // NOI18N |
| Collections.<ExpressionTree>emptyList()); |
| paramModifier = maker.Modifiers(paramModifier, |
| Collections.singletonList(annotation)); |
| VariableTree asyncParam = maker.Variable(paramModifier, asyncReponseParam, |
| maker.QualIdent("javax.ws.rs.container.AsyncResponse"), null);//NOI18N |
| List<VariableTree> params = new ArrayList<VariableTree>(parameters.size()+1); |
| params.add(asyncParam); |
| |
| Tree returnType = method.getReturnType(); |
| boolean noReturn =returnType.toString().equals("void"); // NOI18N |
| |
| StringBuilder body = new StringBuilder("{"); // NOI18N |
| if ( !isEjb ){ |
| body.append(service); |
| body.append(".submit(new Runnable() { public void run() {");//NOI18N |
| } |
| if ( !noReturn ){ |
| body.append(asyncReponseParam); |
| body.append(".resume("); // NOI18N |
| } |
| body.append(movedName); |
| body.append('('); |
| for (VariableTree param : parameters) { |
| ModifiersTree modifier = maker.addModifiersModifier(param.getModifiers(), |
| Modifier.FINAL); |
| VariableTree newParam = maker.Variable(modifier, param.getName(), |
| param.getType(), param.getInitializer()); |
| params.add(newParam); |
| TreePath pathParam = copy.getTrees().getPath( |
| copy.getCompilationUnit(), param); |
| body.append(copy.getTrees().getElement(pathParam).getSimpleName()); |
| body.append(','); |
| } |
| if ( !parameters.isEmpty()){ |
| body.deleteCharAt(body.length()-1); |
| } |
| if ( noReturn){ |
| body.append(");"); |
| body.append(asyncReponseParam); |
| body.append(".resume(javax.ws.rs.core.Response.ok().build());");//NOI18N |
| } |
| else { |
| body.append("));"); |
| } |
| if ( !isEjb ){ |
| body.append("}});"); |
| } |
| body.append('}'); |
| |
| MethodTree newMethod = maker.Method(modifiers, asyncName, |
| maker.Type("void"), // NOI18N |
| Collections.<TypeParameterTree> emptyList(), |
| params, |
| Collections.<ExpressionTree> emptyList(), |
| body.toString(),null); |
| return maker.addClassMember(classTree, newMethod); |
| } |
| |
| private String getAsynParam(String paramName, List<? extends VariableTree> parameters ) { |
| for (VariableTree variableTree : parameters) { |
| if ( paramName.equals(variableTree.getName())){ |
| return getAsynParam(paramName+1 ,parameters); |
| } |
| } |
| return paramName; |
| } |
| |
| private ClassTree moveRestMethod( TreeMaker maker, String movedName, |
| MethodTree method, WorkingCopy copy, ClassTree classTree) |
| { |
| List<? extends VariableTree> parameters = method.getParameters(); |
| Tree returnType = method.getReturnType(); |
| BlockTree body = method.getBody(); |
| |
| ModifiersTree modifiers = maker.Modifiers(EnumSet.of(Modifier.PRIVATE)); |
| MethodTree newMethod = maker.Method(modifiers, movedName, |
| returnType, |
| Collections.<TypeParameterTree> emptyList(), |
| parameters, |
| Collections.<ExpressionTree> emptyList(),body,null); |
| |
| ClassTree newClass = maker.addClassMember(classTree, newMethod); |
| newClass = maker.removeClassMember(newClass, method); |
| return newClass; |
| } |
| } |