blob: 61718c225677d6d9b3595d7738a04fccd3a8b10f [file] [log] [blame]
/*
* 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.maven.jaxws.actions;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
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.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.swing.SwingUtilities;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.Comment;
import org.netbeans.api.java.source.Comment.Style;
import org.netbeans.api.java.source.ui.ScanDialog;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.modules.websvc.api.support.java.GenerationUtils;
import org.netbeans.modules.websvc.api.support.java.SourceUtils;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.cookies.SaveCookie;
import org.openide.loaders.DataObject;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import static org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.j2ee.core.api.support.java.method.MethodCustomizer;
import org.netbeans.modules.j2ee.core.api.support.java.method.MethodCustomizerFactory;
//import org.netbeans.modules.j2ee.api.ejbjar.EjbJar;
import org.netbeans.modules.j2ee.core.api.support.java.method.MethodModel;
import org.netbeans.modules.j2ee.core.api.support.java.method.MethodModelSupport;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
/**
* Helper for adding WS Operation to Web Service.
* @author Milan Kuchtiak
*/
public class AddWsOperationHelper {
private static final ClassPath EMPTY_PATH = ClassPathSupport.createClassPath(new URL[0]);
private final String name;
private final boolean createAnnotations;
private MethodModel method;
public AddWsOperationHelper(String name, boolean flag) {
this.name = name;
this.createAnnotations = flag;
}
public AddWsOperationHelper(String name) {
this(name,true);
}
protected MethodModel getPrototypeMethod() {
return MethodModel.create(
NbBundle.getMessage(AddWsOperationHelper.class,"TXT_DefaultOperationName"), //NOI18N
"java.lang.String", //NOI18N
"",
Collections.<MethodModel.Variable>emptyList(),
Collections.<String>emptyList(),
Collections.<Modifier>emptySet()
);
}
public String getTitle() {
return name;
}
protected MethodCustomizer createDialog(FileObject fileObject, MethodModel methodModel) throws IOException {
return MethodCustomizerFactory.operationMethod(
getTitle(),
methodModel,
ClasspathInfo.create(
ClassPath.getClassPath(fileObject, ClassPath.BOOT), // JDK classes
ClassPath.getClassPath(fileObject, ClassPath.COMPILE), // classpath from dependent projects and libraries
ClassPath.getClassPath(fileObject, ClassPath.SOURCE)), // source classpath
getExistingMethods(fileObject));
}
public void addMethod(FileObject fileObject, String className) throws IOException {
if (className == null) {
return;
}
method = getPrototypeMethod();
MethodCustomizer methodCustomizer = createDialog(fileObject, method);
if (methodCustomizer.customizeMethod()) {
try {
method = methodCustomizer.getMethodModel();
okButtonPressed(method, fileObject, className);
} catch (IOException ioe) {
ErrorManager.getDefault().notify(ioe);
}
}
else{ //user pressed cancel button
method = null;
}
}
/**
* Variant of addMethod(FileObject, String)which returns the final MethodModel.
*/
public MethodModel getMethodModel(FileObject fileObject, String className) throws IOException{
addMethod(fileObject, className);
return method;
}
protected void okButtonPressed(MethodModel method, FileObject implClassFo, String className) throws IOException {
addOperation(method, implClassFo);
}
// protected FileObject getDDFile(FileObject fileObject) {
// return EjbJar.getEjbJar(fileObject).getDeploymentDescriptor();
// }
/*
* Adds a method definition to the the implementation class
*/
private void addOperation(final MethodModel methodModel, final FileObject implClassFo) {
final JavaSource targetSource = JavaSource.forFileObject(implClassFo);
final ProgressHandle handle = ProgressHandleFactory.createHandle(NbBundle.getMessage(AddWsOperationHelper.class, "MSG_AddingNewOperation", methodModel.getName()));
handle.start(100);
final String[] seiClass = new String[1];
final CancellableTask<WorkingCopy> modificationTask = new CancellableTask<WorkingCopy>() {
@Override
public void run(WorkingCopy workingCopy) throws IOException {
workingCopy.toPhase(Phase.RESOLVED);
MethodTree method = MethodModelSupport.createMethodTree(workingCopy, methodModel);
if (method!=null) {
TreeMaker make = workingCopy.getTreeMaker();
TypeElement typeElement = SourceUtils.getPublicTopLevelElement(workingCopy);
if (typeElement!=null) {
boolean increaseProgress = true;
if (createAnnotations) {
if (seiClass[0] == null) {
seiClass[0] = getEndpointInterface(typeElement, workingCopy);
} else {
seiClass[0] = null;
increaseProgress = false;
}
}
if (increaseProgress) handle.progress(20);
ClassTree javaClass = workingCopy.getTrees().getTree(typeElement);
TypeElement webMethodAn = workingCopy.getElements().getTypeElement("javax.jws.WebMethod"); //NOI18N
TypeElement webParamAn = workingCopy.getElements().getTypeElement("javax.jws.WebParam"); //NOI18N
// Public modifier
ModifiersTree modifiersTree = make.Modifiers(
Collections.<Modifier>singleton(Modifier.PUBLIC),
Collections.<AnnotationTree>emptyList()
);
// add @WebMethod annotation
if(createAnnotations && seiClass[0] == null) {
String methodName = method.getName().toString();
// find value for @WebMethod:oparationName
String operationName = findNewOperationName(typeElement, workingCopy, methodName);
AssignmentTree opName = make.Assignment(make.Identifier("operationName"), make.Literal(operationName)); //NOI18N
AnnotationTree webMethodAnnotation = make.Annotation(
make.QualIdent(webMethodAn),
Collections.<ExpressionTree>singletonList(opName)
);
modifiersTree = make.addModifiersAnnotation(modifiersTree, webMethodAnnotation);
// add @Oneway annotation
boolean isOneWay = false;
if (Kind.PRIMITIVE_TYPE == method.getReturnType().getKind()) {
PrimitiveTypeTree primitiveType = (PrimitiveTypeTree)method.getReturnType();
if (TypeKind.VOID == primitiveType.getPrimitiveTypeKind()) {
if (method.getThrows().size() == 0) {
isOneWay = true;
TypeElement oneWayAn = workingCopy.getElements().getTypeElement("javax.jws.Oneway"); //NOI18N
AnnotationTree oneWayAnnotation = make.Annotation(
make.QualIdent(oneWayAn),
Collections.<ExpressionTree>emptyList()
);
modifiersTree = make.addModifiersAnnotation(modifiersTree, oneWayAnnotation);
}
}
}
if (!methodName.equals(operationName)) {
// generate Request/Response wrapper annotations to avoid class conflicts
// this enables to generate operations with identical method names
String packagePrefix = getPackagePrefix(typeElement.getQualifiedName().toString());
TypeElement reqWrapperAn = workingCopy.getElements().getTypeElement("javax.xml.ws.RequestWrapper"); //NOI18N
AssignmentTree className = make.Assignment(make.Identifier("className"), make.Literal(packagePrefix+operationName)); //NOI18N
AnnotationTree reqWrapperAnnotation = make.Annotation(
make.QualIdent(reqWrapperAn),
Collections.<ExpressionTree>singletonList(className)
);
modifiersTree = make.addModifiersAnnotation(modifiersTree, reqWrapperAnnotation);
if (!isOneWay) {
TypeElement resWrapperAn = workingCopy.getElements().getTypeElement("javax.xml.ws.ResponseWrapper"); //NOI18N
className = make.Assignment(make.Identifier("className"), make.Literal(packagePrefix+operationName+"Response")); //NOI18N
AnnotationTree resWrapperAnnotation = make.Annotation(
make.QualIdent(resWrapperAn),
Collections.<ExpressionTree>singletonList(className)
);
modifiersTree = make.addModifiersAnnotation(modifiersTree, resWrapperAnnotation);
}
}
}
if (increaseProgress) handle.progress(40);
// add @WebParam annotations
List<? extends VariableTree> parameters = method.getParameters();
List<VariableTree> newParameters = new ArrayList<VariableTree>();
if(createAnnotations && seiClass[0] == null) {
for (VariableTree param:parameters) {
AnnotationTree paramAnnotation = make.Annotation(
make.QualIdent(webParamAn),
Collections.<ExpressionTree>singletonList(
make.Assignment(make.Identifier("name"), make.Literal(param.getName().toString()))) //NOI18N
);
GenerationUtils genUtils = GenerationUtils.newInstance(workingCopy);
newParameters.add(genUtils.addAnnotation(param, paramAnnotation));
}
} else {
newParameters.addAll(parameters);
}
if (increaseProgress) handle.progress(70);
// create new (annotated) method
MethodTree annotatedMethod = typeElement.getKind() == ElementKind.CLASS ?
make.Method(
modifiersTree,
method.getName(),
method.getReturnType(),
method.getTypeParameters(),
newParameters,
method.getThrows(),
getMethodBody(method.getReturnType()), //NOI18N
(ExpressionTree)method.getDefaultValue()) :
make.Method(
modifiersTree,
method.getName(),
method.getReturnType(),
method.getTypeParameters(),
newParameters,
method.getThrows(),
(BlockTree)null,
(ExpressionTree)method.getDefaultValue());
Comment comment = Comment.create(Style.JAVADOC, -2, -2, -2,
NbBundle.getMessage(AddWsOperationHelper.class, "TXT_WSOperation"));
make.addComment(annotatedMethod, comment, true);
if (increaseProgress) handle.progress(90);
ClassTree modifiedClass = make.addClassMember(javaClass,annotatedMethod);
workingCopy.rewrite(javaClass, modifiedClass);
}
}
}
@Override
public void cancel() {
}
};
final Runnable runnable = new Runnable() {
@Override
public void run() {
doAddOperation(implClassFo, targetSource, handle, seiClass,
modificationTask);
}
};
final String title = NbBundle.getMessage(AddWsOperationHelper.class,
"LBL_AddOperation"); // NOI18N
if (SwingUtilities.isEventDispatchThread()) {
ScanDialog.runWhenScanFinished( runnable, title );
} else {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
ScanDialog.runWhenScanFinished( runnable, title );
}
});
}
}
private void doAddOperation( final FileObject implClassFo,
final JavaSource targetSource, final ProgressHandle handle,
final String[] seiClass,
final CancellableTask<WorkingCopy> modificationTask )
{
RequestProcessor.getDefault().post(new Runnable() {
@Override
public void run() {
try {
targetSource.runModificationTask(modificationTask).commit();
// add method to SEI class
if (seiClass[0] != null) {
ClassPath sourceCP = ClassPath.getClassPath(implClassFo, ClassPath.SOURCE);
FileObject seiFo = sourceCP.findResource(seiClass[0].replace('.', '/')+".java"); //NOI18N
if (seiFo != null) {
JavaSource seiSource = JavaSource.forFileObject(seiFo);
seiSource.runModificationTask(modificationTask).commit();
saveFile(seiFo);
}
}
saveFile(implClassFo);
} catch (IOException ex) {
ErrorManager.getDefault().notify(ex);
} finally {
handle.finish();
}
}
});
}
private String getEndpointInterface(TypeElement classEl, CompilationController controller) {
TypeElement wsElement = controller.getElements().getTypeElement("javax.jws.WebService"); //NOI18N
if (wsElement != null) {
List<? extends AnnotationMirror> annotations = classEl.getAnnotationMirrors();
for (AnnotationMirror anMirror : annotations) {
if (controller.getTypes().isSameType(wsElement.asType(), anMirror.getAnnotationType())) {
Map<? extends ExecutableElement, ? extends AnnotationValue> expressions = anMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry: expressions.entrySet()) {
if (entry.getKey().getSimpleName().contentEquals("endpointInterface")) { //NOI18N
String value = (String) expressions.get(entry.getKey()).getValue();
if (value != null) {
TypeElement seiEl = controller.getElements().getTypeElement(value);
if (seiEl != null) {
return seiEl.getQualifiedName().toString();
}
}
}
}
} // end if
}
}
return null;
}
private String findNewOperationName(TypeElement classEl, CompilationController controller, String suggestedMethodName)
throws IOException {
TypeElement methodElement = controller.getElements().getTypeElement("javax.jws.WebMethod"); //NOI18N
Set<String> operationNames = new HashSet<String>();
if (methodElement != null) {
List<ExecutableElement> methods = getMethods(controller,classEl);
for (ExecutableElement m:methods) {
String opName = null;
List<? extends AnnotationMirror> annotations = m.getAnnotationMirrors();
for (AnnotationMirror anMirror : annotations) {
if (controller.getTypes().isSameType(methodElement.asType(), anMirror.getAnnotationType())) {
Map<? extends ExecutableElement, ? extends AnnotationValue> expressions = anMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry: expressions.entrySet()) {
if (entry.getKey().getSimpleName().contentEquals("operationName")) { //NOI18N
opName = (String) expressions.get(entry.getKey()).getValue();
break;
}
}
} // end if
if (opName != null) break;
} //enfd for
if (opName == null) opName = m.getSimpleName().toString();
operationNames.add(opName);
}
}
return findNewOperationName(operationNames, suggestedMethodName);
}
private String findNewOperationName(Set<String> operationNames, String suggestedMethodName) {
int i=0;
String newName = suggestedMethodName; //NOI18N
while(operationNames.contains(newName)) {
newName = suggestedMethodName+"_"+String.valueOf(++i); //NOI18N
}
return newName;
}
private String getPackagePrefix (String className) {
int lastDot = className.indexOf("."); //NOI18N
if (lastDot > 0) return className.substring(0,lastDot+1);
else return "";
}
private void saveFile(FileObject file) throws IOException {
DataObject dataObject = DataObject.find(file);
if (dataObject!=null) {
SaveCookie cookie = dataObject.getCookie(SaveCookie.class);
if (cookie!=null) cookie.save();
}
}
private String getMethodBody(Tree returnType) {
String body = null;
if (Kind.PRIMITIVE_TYPE == returnType.getKind()) {
TypeKind type = ((PrimitiveTypeTree)returnType).getPrimitiveTypeKind();
if (TypeKind.VOID == type) body = ""; //NOI18N
else if (TypeKind.BOOLEAN == type) body = "return false;"; // NOI18N
else if (TypeKind.INT == type) body = "return 0;"; // NOI18N
else if (TypeKind.LONG == type) body = "return 0;"; // NOI18N
else if (TypeKind.FLOAT == type) body = "return 0.0;"; // NOI18N
else if (TypeKind.DOUBLE == type) body = "return 0.0;"; // NOI18N
else if (TypeKind.BYTE == type) body = "return 0;"; // NOI18N
else if (TypeKind.SHORT == type) body = "return 0;"; // NOI18N
else if (TypeKind.CHAR == type) body = "return ' ';"; // NOI18N
else body = "return null"; //NOI18N
} else
body = "return null"; //NOI18N
return "{\n\t\t"+NbBundle.getMessage(AddWsOperationHelper.class, "TXT_TodoComment")+"\n"+body+"\n}";
}
/*
protected static MethodsNode getMethodsNode() {
Node[] nodes = Utilities.actionsGlobalContext().lookup(new Lookup.Template<Node>(Node.class)).allInstances().toArray(new Node[0]);
if (nodes.length != 1) {
return null;
}
return nodes[0].getLookup().lookup(MethodsNode.class);
}
*/
private Collection<MethodModel> getExistingMethods(FileObject implClass) {
final JavaSource javaSource = JavaSource.forFileObject(implClass);
final ResultHolder<MethodModel> result = new ResultHolder<MethodModel>();
if (javaSource!=null) {
final CancellableTask<CompilationController> task =
new CancellableTask<CompilationController>()
{
@Override
public void run(CompilationController controller) throws IOException {
controller.toPhase(Phase.ELEMENTS_RESOLVED);
TypeElement typeElement = SourceUtils.
getPublicTopLevelElement(controller);
Collection<MethodModel> wsOperations = new ArrayList<MethodModel>();
if (typeElement!=null) {
// find methods
List<ExecutableElement> allMethods = getMethods(controller,
typeElement);
boolean foundWebMethodAnnotation=false;
for(ExecutableElement method:allMethods) {
// check if return type is a valid type
if (method.getReturnType().getKind() == TypeKind.ERROR) break;
// check if param types are valid types
boolean validParamTypes = true;
List<? extends VariableElement> params = method.getParameters();
for (VariableElement param:params) {
if (param.asType().getKind() == TypeKind.ERROR) {
validParamTypes = false;
break;
}
}
if (validParamTypes) {
MethodModel methodModel = MethodModelSupport.
createMethodModel(controller, method);
wsOperations.add(methodModel);
}
} // for
}
result.setResult(wsOperations);
}
@Override
public void cancel() {}
};
final Runnable runnable = new Runnable() {
@Override
public void run() {
try {
javaSource.runUserActionTask(task, true);
} catch (IOException ex) {
ErrorManager.getDefault().notify(ex);
}
}
};
final String title = NbBundle.getMessage(AddWsOperationHelper.class,
"LBL_FindMethods") ; // NOI18N
if ( SwingUtilities.isEventDispatchThread() ){
ScanDialog.runWhenScanFinished(runnable,title );
}
else {
try {
SwingUtilities.invokeAndWait( new Runnable() {
@Override
public void run() {
ScanDialog.runWhenScanFinished(runnable,title );
}
});
}
catch (InvocationTargetException e ){
ErrorManager.getDefault().notify(e);
}
catch( InterruptedException e ){
ErrorManager.getDefault().notify(e);
}
}
}
return result.getResult();
}
private List<ExecutableElement> getMethods(CompilationController controller, TypeElement classElement) throws IOException {
List<? extends Element> members = classElement.getEnclosedElements();
List<ExecutableElement> methods = ElementFilter.methodsIn(members);
List<ExecutableElement> publicMethods = new ArrayList<ExecutableElement>();
for (ExecutableElement m:methods) {
//Set<Modifier> modifiers = method.getModifiers();
//if (modifiers.contains(Modifier.PUBLIC)) {
publicMethods.add(m);
//}
}
return publicMethods;
}
/** Holder class for result
*/
private static class ResultHolder<E> {
private Collection<E> result;
public Collection<E> getResult() {
return result;
}
public void setResult(Collection<E> result) {
this.result=result;
}
}
}