1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.internal.ws.processor.modeler.annotation;
  27 
  28 import com.sun.istack.internal.logging.Logger;
  29 import com.sun.tools.internal.ws.processor.generator.GeneratorUtil;
  30 import com.sun.tools.internal.ws.processor.modeler.ModelerException;
  31 import com.sun.tools.internal.ws.resources.WebserviceapMessages;
  32 import com.sun.tools.internal.ws.wscompile.AbortException;
  33 import com.sun.tools.internal.ws.wscompile.WsgenOptions;
  34 
  35 import javax.annotation.processing.AbstractProcessor;
  36 import javax.annotation.processing.ProcessingEnvironment;
  37 import javax.annotation.processing.RoundEnvironment;
  38 import javax.annotation.processing.SupportedAnnotationTypes;
  39 import javax.annotation.processing.SupportedOptions;
  40 import javax.jws.WebService;
  41 import javax.lang.model.SourceVersion;
  42 import javax.lang.model.element.Element;
  43 import javax.lang.model.element.ElementKind;
  44 import javax.lang.model.element.Name;
  45 import javax.lang.model.element.TypeElement;
  46 import javax.lang.model.type.TypeMirror;
  47 import javax.lang.model.util.ElementFilter;
  48 import javax.tools.Diagnostic;
  49 import javax.xml.ws.Holder;
  50 import javax.xml.ws.WebServiceProvider;
  51 import java.io.ByteArrayOutputStream;
  52 import java.io.File;
  53 import java.io.PrintStream;
  54 import java.lang.reflect.Method;
  55 import java.rmi.Remote;
  56 import java.rmi.RemoteException;
  57 import java.util.ArrayList;
  58 import java.util.Collection;
  59 import java.util.HashSet;
  60 import java.util.Scanner;
  61 import java.util.Set;
  62 import java.util.logging.Level;
  63 
  64 /**
  65  * WebServiceAp is a AnnotationProcessor for processing javax.jws.* and
  66  * javax.xml.ws.* annotations. This class is used either by the WsGen (CompileTool) tool or
  67  * indirectly when invoked by javac.
  68  *
  69  * @author WS Development Team
  70  */
  71 @SupportedAnnotationTypes({
  72         "javax.jws.HandlerChain",
  73         "javax.jws.Oneway",
  74         "javax.jws.WebMethod",
  75         "javax.jws.WebParam",
  76         "javax.jws.WebResult",
  77         "javax.jws.WebService",
  78         "javax.jws.soap.InitParam",
  79         "javax.jws.soap.SOAPBinding",
  80         "javax.jws.soap.SOAPMessageHandler",
  81         "javax.jws.soap.SOAPMessageHandlers",
  82         "javax.xml.ws.BindingType",
  83         "javax.xml.ws.RequestWrapper",
  84         "javax.xml.ws.ResponseWrapper",
  85         "javax.xml.ws.ServiceMode",
  86         "javax.xml.ws.WebEndpoint",
  87         "javax.xml.ws.WebFault",
  88         "javax.xml.ws.WebServiceClient",
  89         "javax.xml.ws.WebServiceProvider",
  90         "javax.xml.ws.WebServiceRef"
  91 })
  92 @SupportedOptions({WebServiceAp.DO_NOT_OVERWRITE, WebServiceAp.IGNORE_NO_WEB_SERVICE_FOUND_WARNING})
  93 public class WebServiceAp extends AbstractProcessor implements ModelBuilder {
  94 
  95     private static final Logger LOGGER = Logger.getLogger(WebServiceAp.class);
  96 
  97     public static final String DO_NOT_OVERWRITE = "doNotOverWrite";
  98     public static final String IGNORE_NO_WEB_SERVICE_FOUND_WARNING = "ignoreNoWebServiceFoundWarning";
  99 
 100     private WsgenOptions options;
 101     protected AnnotationProcessorContext context;
 102     private File sourceDir;
 103     private boolean doNotOverWrite;
 104     private boolean ignoreNoWebServiceFoundWarning = false;
 105     private TypeElement remoteElement;
 106     private TypeMirror remoteExceptionElement;
 107     private TypeMirror exceptionElement;
 108     private TypeMirror runtimeExceptionElement;
 109     private TypeElement defHolderElement;
 110     private boolean isCommandLineInvocation;
 111     private PrintStream out;
 112     private Collection<TypeElement> processedTypeElements = new HashSet<TypeElement>();
 113 
 114     public WebServiceAp() {
 115         this.context = new AnnotationProcessorContext();
 116     }
 117 
 118     public WebServiceAp(WsgenOptions options, PrintStream out) {
 119         this.options = options;
 120         this.sourceDir = (options != null) ? options.sourceDir : null;
 121         this.doNotOverWrite = (options != null) && options.doNotOverWrite;
 122         this.context = new AnnotationProcessorContext();
 123         this.out = out;
 124     }
 125 
 126     @Override
 127     public synchronized void init(ProcessingEnvironment processingEnv) {
 128         super.init(processingEnv);
 129         remoteElement = processingEnv.getElementUtils().getTypeElement(Remote.class.getName());
 130         remoteExceptionElement = processingEnv.getElementUtils().getTypeElement(RemoteException.class.getName()).asType();
 131         exceptionElement = processingEnv.getElementUtils().getTypeElement(Exception.class.getName()).asType();
 132         runtimeExceptionElement = processingEnv.getElementUtils().getTypeElement(RuntimeException.class.getName()).asType();
 133         defHolderElement = processingEnv.getElementUtils().getTypeElement(Holder.class.getName());
 134         if (options == null) {
 135             options = new WsgenOptions();
 136 
 137             out = new PrintStream(new ByteArrayOutputStream());
 138 
 139             doNotOverWrite = getOption(DO_NOT_OVERWRITE);
 140             ignoreNoWebServiceFoundWarning = getOption(IGNORE_NO_WEB_SERVICE_FOUND_WARNING);
 141 
 142             String classDir = parseArguments();
 143             String property = System.getProperty("java.class.path");
 144             options.classpath = classDir + File.pathSeparator + (property != null ? property : "");
 145             isCommandLineInvocation = true;
 146         }
 147         options.filer = processingEnv.getFiler();
 148     }
 149 
 150     private String parseArguments() {
 151         // let's try to parse JavacOptions
 152 
 153         String classDir = null;
 154         try {
 155             ClassLoader cl = WebServiceAp.class.getClassLoader();
 156             Class javacProcessingEnvironmentClass = Class.forName("com.sun.tools.javac.processing.JavacProcessingEnvironment", false, cl);
 157             if (javacProcessingEnvironmentClass.isInstance(processingEnv)) {
 158                 Method getContextMethod = javacProcessingEnvironmentClass.getDeclaredMethod("getContext");
 159                 Object tmpContext = getContextMethod.invoke(processingEnv);
 160                 Class optionsClass = Class.forName("com.sun.tools.javac.util.Options", false, cl);
 161                 Class contextClass = Class.forName("com.sun.tools.javac.util.Context", false, cl);
 162                 Method instanceMethod = optionsClass.getDeclaredMethod("instance", new Class[]{contextClass});
 163                 Object tmpOptions = instanceMethod.invoke(null, tmpContext);
 164                 if (tmpOptions != null) {
 165                     Method getMethod = optionsClass.getDeclaredMethod("get", new Class[]{String.class});
 166                     Object result = getMethod.invoke(tmpOptions, "-s"); // todo: we have to check for -d also
 167                     if (result != null) {
 168                         classDir = (String) result;
 169                     }
 170                     this.options.verbose = getMethod.invoke(tmpOptions, "-verbose") != null;
 171                 }
 172             }
 173         } catch (Exception e) {
 174             /// some Error was here - problems with reflection or security
 175             processWarning(WebserviceapMessages.WEBSERVICEAP_PARSING_JAVAC_OPTIONS_ERROR());
 176             report(e.getMessage());
 177         }
 178 
 179         if (classDir == null) { // some error within reflection block
 180             String property = System.getProperty("sun.java.command");
 181             if (property != null) {
 182                 Scanner scanner = new Scanner(property);
 183                 boolean sourceDirNext = false;
 184                 while (scanner.hasNext()) {
 185                     String token = scanner.next();
 186                     if (sourceDirNext) {
 187                         classDir = token;
 188                         sourceDirNext = false;
 189                     } else if ("-verbose".equals(token)) {
 190                         options.verbose = true;
 191                     } else if ("-s".equals(token)) {
 192                         sourceDirNext = true;
 193                     }
 194                 }
 195             }
 196         }
 197         if (classDir != null) {
 198             sourceDir = new File(classDir);
 199         }
 200         return classDir;
 201     }
 202 
 203     private boolean getOption(String key) {
 204         String value = processingEnv.getOptions().get(key);
 205         if (value != null) {
 206             return Boolean.valueOf(value);
 207         }
 208         return false;
 209     }
 210 
 211     @Override
 212     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 213         if (context.getRound() != 1) {
 214             return true;
 215         }
 216         context.incrementRound();
 217         WebService webService;
 218         WebServiceProvider webServiceProvider;
 219         WebServiceVisitor webServiceVisitor = new WebServiceWrapperGenerator(this, context);
 220         boolean processedEndpoint = false;
 221         Collection<TypeElement> classes = new ArrayList<TypeElement>();
 222         filterClasses(classes, roundEnv.getRootElements());
 223         for (TypeElement element : classes) {
 224             webServiceProvider = element.getAnnotation(WebServiceProvider.class);
 225             webService = element.getAnnotation(WebService.class);
 226             if (webServiceProvider != null) {
 227                 if (webService != null) {
 228                     processError(WebserviceapMessages.WEBSERVICEAP_WEBSERVICE_AND_WEBSERVICEPROVIDER(element.getQualifiedName()));
 229                 }
 230                 processedEndpoint = true;
 231             }
 232 
 233             if (webService == null) {
 234                 continue;
 235             }
 236 
 237             element.accept(webServiceVisitor, null);
 238             processedEndpoint = true;
 239         }
 240         if (!processedEndpoint) {
 241             if (isCommandLineInvocation) {
 242                 if (!ignoreNoWebServiceFoundWarning) {
 243                     processWarning(WebserviceapMessages.WEBSERVICEAP_NO_WEBSERVICE_ENDPOINT_FOUND());
 244                 }
 245             } else {
 246                 processError(WebserviceapMessages.WEBSERVICEAP_NO_WEBSERVICE_ENDPOINT_FOUND());
 247             }
 248         }
 249         return true;
 250     }
 251 
 252     private void filterClasses(Collection<TypeElement> classes, Collection<? extends Element> elements) {
 253         for (Element element : elements) {
 254             if (element.getKind().equals(ElementKind.CLASS)) {
 255                 classes.add((TypeElement) element);
 256                 filterClasses(classes, ElementFilter.typesIn(element.getEnclosedElements()));
 257             }
 258         }
 259     }
 260 
 261     @Override
 262     public void processWarning(String message) {
 263         if (isCommandLineInvocation) {
 264             processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
 265         } else {
 266             report(message);
 267         }
 268     }
 269 
 270     protected void report(String msg) {
 271         if (out == null) {
 272             if (LOGGER.isLoggable(Level.FINE)) {
 273                 LOGGER.log(Level.FINE, "No output set for web service annotation processor reporting.");
 274             }
 275             return;
 276         }
 277         out.println(msg);
 278         out.flush();
 279     }
 280 
 281     @Override
 282     public void processError(String message) {
 283         if (isCommandLineInvocation) {
 284             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
 285             throw new AbortException();
 286         } else {
 287             throw new ModelerException(message);
 288         }
 289     }
 290 
 291     @Override
 292     public void processError(String message, Element element) {
 293         if (isCommandLineInvocation) {
 294             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
 295         } else {
 296             throw new ModelerException(message);
 297         }
 298     }
 299 
 300     @Override
 301     public boolean canOverWriteClass(String className) {
 302         return !((doNotOverWrite && GeneratorUtil.classExists(options, className)));
 303     }
 304 
 305     @Override
 306     public File getSourceDir() {
 307         return sourceDir;
 308     }
 309 
 310     @Override
 311     public boolean isRemote(TypeElement typeElement) {
 312         return processingEnv.getTypeUtils().isSubtype(typeElement.asType(), remoteElement.asType());
 313     }
 314 
 315     @Override
 316     public boolean isServiceException(TypeMirror typeMirror) {
 317         return processingEnv.getTypeUtils().isSubtype(typeMirror, exceptionElement)
 318                 && !processingEnv.getTypeUtils().isSubtype(typeMirror, runtimeExceptionElement)
 319                 && !processingEnv.getTypeUtils().isSubtype(typeMirror, remoteExceptionElement);
 320     }
 321 
 322     @Override
 323     public TypeMirror getHolderValueType(TypeMirror type) {
 324         return TypeModeler.getHolderValueType(type, defHolderElement, processingEnv);
 325     }
 326 
 327     @Override
 328     public boolean checkAndSetProcessed(TypeElement typeElement) {
 329         if (!processedTypeElements.contains(typeElement)) {
 330             processedTypeElements.add(typeElement);
 331             return false;
 332         }
 333         return true;
 334     }
 335 
 336     @Override
 337     public void log(String message) {
 338         if (options != null && options.verbose) {
 339             message = new StringBuilder().append('[').append(message).append(']').toString(); // "[%s]"
 340             processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
 341         }
 342     }
 343 
 344     @Override
 345     public WsgenOptions getOptions() {
 346         return options;
 347     }
 348 
 349     @Override
 350     public ProcessingEnvironment getProcessingEnvironment() {
 351         return processingEnv;
 352     }
 353 
 354     @Override
 355     public String getOperationName(Name messageName) {
 356         return messageName != null ? messageName.toString() : null;
 357     }
 358 
 359     @Override
 360     public SourceVersion getSupportedSourceVersion() {
 361         return SourceVersion.latest();
 362     }
 363 }