com.ebmwebsourcing.petals.common.internal.provisional.utils.JaxWsUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.ebmwebsourcing.petals.common.internal.provisional.utils.JaxWsUtils.java

Source

/******************************************************************************
 * Copyright (c) 2010-2013, Linagora
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *       Linagora - initial API and implementation
 *******************************************************************************/

package com.ebmwebsourcing.petals.common.internal.provisional.utils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMInstallType;
import org.eclipse.jdt.launching.JavaRuntime;

import com.ebmwebsourcing.petals.common.internal.PetalsCommonPlugin;
import com.ebmwebsourcing.petals.common.internal.provisional.preferences.PreferencesManager;

/**
 * Utility methods related to JAX-WS.
 * @author Vincent Zurczak - EBM WebSourcing
 */
public final class JaxWsUtils {

    /**
     * A file to store the generated classes.
     */
    private static final File TEMP_FILE = new File(System.getProperty("java.io.tmpdir"), "Petals-JAX-WS");

    /**
     * The unique instance of this class.
     */
    public static final JaxWsUtils INSTANCE = new JaxWsUtils();

    /**
     * Constructor.
     */
    private JaxWsUtils() {
        // nothing
    }

    /**
     * Gets a JAX-WS executable, that comes with a JDK 6.
     * <p>
     * This method can be used to check that there is an available JDK 6
     * that will be used by the tooling.
     * </p>
     *
     * @param wsGen true to get WsGen, false to get WsImport
     * @return the required executable file
     * @throws IOException if the executable was not found
     */
    public static File getJavaExecutable(boolean wsGen) throws IOException {

        final String winExecName;
        final String linuxExecName;
        if (wsGen) {
            winExecName = "bin/wsgen.exe";
            linuxExecName = "bin/wsgen";
        } else {
            winExecName = "bin/wsimport.exe";
            linuxExecName = "bin/wsimport";
        }

        File result = null;
        List<String> locations = new ArrayList<String>();

        // Add all the locations where to search
        // Use the environment variables
        String javaHome = System.getProperty("java.home");
        if (javaHome != null)
            locations.add(javaHome);

        javaHome = System.getenv("JAVA_HOME");
        if (javaHome != null)
            locations.add(javaHome);

        // Use the registered VM
        IVMInstallType[] vmInstallTypes = JavaRuntime.getVMInstallTypes();
        for (IVMInstallType vmInstallType : vmInstallTypes) {
            IVMInstall[] vmInstalls = vmInstallType.getVMInstalls();
            for (IVMInstall vmInstall : vmInstalls)
                locations.add(vmInstall.getInstallLocation().getAbsolutePath());
        }

        // Search the executables in these locations
        for (String location : locations) {
            File executable;
            if ((executable = new File(location, winExecName)).exists())
                result = executable;
            else if ((executable = new File(location, linuxExecName)).exists())
                result = executable;

            if (result != null)
                break;
        }

        if (result == null) {
            if (wsGen)
                throw new IOException("Wsgen could not be found. Make sure you have a JDK 6 or higher.");
            else
                throw new IOException("Wsimport could not be found. Make sure you have a JDK 6 or higher.");
        }

        return result;
    }

    /**
     * Generates a WSDL from a JAX-WS annotated class.
     * <p>
     * If an error occurs during the execution, it will appear in the returned
     * string builder.
     * </p>
     *
     * @param className the name of the annotated class
     * @param targetDirectory the target directory
     * @param jp the Java project that contains this class
     * @return a string builder containing the execution details
     * @throws IOException if WSgen was not found, or if the process could not be started
     * @throws InterruptedException
     * @throws JaxWsException if the generation seems to have failed
     */
    public StringBuilder generateWsdl(String className, File targetDirectory, IJavaProject jp)
            throws IOException, InterruptedException, JaxWsException {

        // Create the temporary file
        synchronized (TEMP_FILE) {
            if (!TEMP_FILE.exists() && !TEMP_FILE.mkdir())
                throw new IOException("The class directories could not be created.");
        }

        final StringBuilder outputBuffer = new StringBuilder();
        int exitValue = 0;
        try {
            File executable = getJavaExecutable(true);

            // Build the class path
            StringBuilder classpath = new StringBuilder();
            String separator = System.getProperty("path.separator");
            for (String entry : JavaUtils.getClasspath(jp, true, true))
                classpath.append(entry + separator);

            IPath binPath = null;
            try {
                binPath = jp.getOutputLocation();
            } catch (JavaModelException e) {
                PetalsCommonPlugin.log(e, IStatus.ERROR);
            }

            if (binPath != null) {
                binPath = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(binPath);
                File binFile = binPath.toFile();
                if (!binFile.exists() && !binFile.mkdir())
                    PetalsCommonPlugin.log("The 'bin' directory could not be created.", IStatus.WARNING);
            }

            // Build the command line
            List<String> cmd = new ArrayList<String>();
            cmd.add(executable.getAbsolutePath());
            cmd.add("-verbose");
            cmd.add("-wsdl");
            cmd.add("-classpath");
            cmd.add(classpath.toString());
            cmd.add("-r");
            cmd.add(targetDirectory.getAbsolutePath());
            cmd.add("-d");
            cmd.add(TEMP_FILE.getAbsolutePath());
            cmd.add("-s");
            cmd.add(TEMP_FILE.getAbsolutePath());
            cmd.add(className);

            // Create the process
            ProcessBuilder pb = new ProcessBuilder(cmd);
            pb = pb.redirectErrorStream(true);
            final Process process = pb.start();

            // Monitor the execution
            Thread loggingThread = new Thread() {
                @Override
                public void run() {
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                        String line = "";
                        try {
                            while ((line = reader.readLine()) != null)
                                outputBuffer.append(line + "\n");
                        } finally {
                            reader.close();
                        }

                    } catch (IOException ioe) {
                        PetalsCommonPlugin.log(ioe, IStatus.ERROR);
                    }
                }
            };
            loggingThread.start();
            loggingThread.join();
            exitValue = process.waitFor();

        } finally {

            // Free the temporary directory
            synchronized (TEMP_FILE) {
                IoUtils.deleteFilesRecursively(TEMP_FILE.listFiles());
            }

            // Keep a trace of the generation
            JaxWsException loggingException;
            if (exitValue != 0) {
                loggingException = new JaxWsException(
                        "The JAX-WS process did not return 0. An error may have occurred.") {
                    private static final long serialVersionUID = -8585870927871804985L;

                    @Override
                    public void printStackTrace(PrintStream s) {
                        s.print(outputBuffer.toString());
                    }

                    @Override
                    public void printStackTrace(PrintWriter s) {
                        s.print(outputBuffer.toString());
                    }
                };

                throw loggingException;

            } else if (PreferencesManager.logAllJaxWsTraces()) {
                loggingException = new JaxWsException(
                        "Logging the WSDL generation trace (" + jp.getProject().getName() + ").") {
                    private static final long serialVersionUID = -5344307338271840633L;

                    @Override
                    public void printStackTrace(PrintStream s) {
                        s.print(outputBuffer.toString());
                    }

                    @Override
                    public void printStackTrace(PrintWriter s) {
                        s.print(outputBuffer.toString());
                    }
                };

                PetalsCommonPlugin.log(loggingException, IStatus.INFO);
            }
        }

        return outputBuffer;
    }

    /**
     * Generates a JAX-WS annotated client from a WSDL file.
     * <p>
     * If an error occurs during the execution, it will appear in the returned
     * string builder.
     * </p>
     *
     * @param wsdlUri the WSDL's URI
     * @param targetDirectory the target directory
     * @return a string builder containing the execution details
     * @throws IOException if WSgen was not found, or if the process could not be started
     * @throws InterruptedException
     * @throws JaxWsException if the generation seems to have failed
     */
    public StringBuilder generateWsClient(URI wsdlUri, File targetDirectory)
            throws IOException, InterruptedException, JaxWsException {

        // Create the temporary file
        synchronized (TEMP_FILE) {
            if (!TEMP_FILE.exists() && !TEMP_FILE.mkdir())
                throw new IOException("The class directories could not be created.");
        }

        final StringBuilder outputBuffer = new StringBuilder();
        int exitValue = 0;
        try {
            File executable = getJavaExecutable(false);

            // Build the command line
            List<String> cmd = new ArrayList<String>();
            cmd.add(executable.getAbsolutePath());
            cmd.add("-verbose");
            cmd.add("-s");
            cmd.add(targetDirectory.getAbsolutePath());
            cmd.add("-d");
            cmd.add(TEMP_FILE.getAbsolutePath());
            cmd.add(wsdlUri.toString());

            // Create the process
            ProcessBuilder pb = new ProcessBuilder(cmd);
            pb = pb.redirectErrorStream(true);
            final Process process = pb.start();

            // Monitor the execution
            Thread loggingThread = new Thread() {
                @Override
                public void run() {
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                        String line = "";
                        try {
                            while ((line = reader.readLine()) != null)
                                outputBuffer.append(line + "\n");
                        } finally {
                            reader.close();
                        }

                    } catch (IOException ioe) {
                        PetalsCommonPlugin.log(ioe, IStatus.ERROR);
                    }
                }
            };
            loggingThread.start();
            loggingThread.join();
            exitValue = process.waitFor();

        } finally {

            // Free the temporary directory
            synchronized (TEMP_FILE) {
                IoUtils.deleteFilesRecursively(TEMP_FILE.listFiles());
            }

            // Keep a trace of the generation
            JaxWsException loggingException;
            if (exitValue != 0) {
                loggingException = new JaxWsException(
                        "The JAX-WS process did not return 0. An error may have occurred.") {
                    private static final long serialVersionUID = -8585870927871804985L;

                    @Override
                    public void printStackTrace(PrintStream s) {
                        s.print(outputBuffer.toString());
                    }

                    @Override
                    public void printStackTrace(PrintWriter s) {
                        s.print(outputBuffer.toString());
                    }
                };

                throw loggingException;

            } else if (PreferencesManager.logAllJaxWsTraces()) {
                loggingException = new JaxWsException("Logging the WSDL import trace (" + wsdlUri + ").") {
                    private static final long serialVersionUID = -5344307338271840633L;

                    @Override
                    public void printStackTrace(PrintStream s) {
                        s.print(outputBuffer.toString());
                    }

                    @Override
                    public void printStackTrace(PrintWriter s) {
                        s.print(outputBuffer.toString());
                    }
                };

                PetalsCommonPlugin.log(loggingException, IStatus.INFO);
            }
        }

        return outputBuffer;
    }

    /**
     * The @WebService annotation (javax.jws.WebService").
     */
    private static final String WEB_WS_ANNOTATION = "javax.jws.WebService";

    /**
     * The shorten @WebService annotation (WebService).
     */
    private static final String SHORT_WEB_WS_ANNOTATION = "WebService";

    /**
     * The @WebServiceClient annotation (javax.jws.WebServiceClient").
     */
    private static final String WEB_WS_CLIENT_ANNOTATION = "javax.xml.ws.WebServiceClient";

    /**
     * Searches all the Java types from a Java project that are annotated with @WebService.
     *
     * @param jp the containing Java project
     * @param monitor the progress monitor
     * @param includeClasses true to include classes in the search results
     * @param includeInterfaces true to include the interfaces in the search results
     * @return a Map
     * <p>
     * Key = the qualified name of the annotated type<br />
     * Value = the associated service name (in the target WSDL)
     * </p>
     *
     * @throws CoreException if the search could not be launched
     */
    public static Map<String, String> getJaxAnnotatedJavaTypes(IJavaProject jp, IProgressMonitor monitor,
            final boolean includeClasses, final boolean includeInterfaces) throws CoreException {

        jp.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
        jp.getProject().build(IncrementalProjectBuilder.FULL_BUILD, monitor);

        final Map<String, String> classNameToServiceName = new HashMap<String, String>();
        SearchPattern pattern = SearchPattern.createPattern(WEB_WS_ANNOTATION, IJavaSearchConstants.ANNOTATION_TYPE,
                IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE,
                SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);

        // This is what we do when we find a match
        SearchRequestor requestor = new SearchRequestor() {
            @Override
            public void acceptSearchMatch(SearchMatch match) throws CoreException {

                // We get the Java type that is annotated with @WebService
                if (match.getElement() instanceof IType) {

                    // Find the annotation
                    IType type = (IType) match.getElement();
                    if (type.isInterface() && includeInterfaces || type.isClass() && includeClasses) {

                        IAnnotation ann = type.getAnnotation(WEB_WS_ANNOTATION);
                        if (!ann.exists())
                            ann = type.getAnnotation(SHORT_WEB_WS_ANNOTATION);

                        // Get the service name and store it
                        if (ann.exists()) {
                            String serviceName = null;
                            for (IMemberValuePair pair : ann.getMemberValuePairs()) {
                                if ("serviceName".equalsIgnoreCase(pair.getMemberName())) {
                                    serviceName = (String) pair.getValue();
                                    break;
                                }
                            }

                            if (serviceName == null)
                                serviceName = type.getElementName() + "Service";

                            classNameToServiceName.put(type.getFullyQualifiedName(), serviceName);
                        }
                    }
                }
            }
        };

        new SearchEngine().search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                SearchEngine.createJavaSearchScope(new IJavaElement[] { jp }, false), requestor, monitor);

        return classNameToServiceName;
    }

    /**
     * Deletes all the classes annotated with @WebServiceClient.
     *
     * @param jp the containing Java project
     * @param monitor the progress monitor
     * @throws CoreException if the classes annotated with @WebServiceClient could not be listed
     */
    public static void removeWebServiceClient(IJavaProject jp, final IProgressMonitor monitor)
            throws CoreException {

        SearchPattern pattern = SearchPattern.createPattern(WEB_WS_CLIENT_ANNOTATION,
                IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE,
                SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);

        // This is what we do when we find a match
        SearchRequestor requestor = new SearchRequestor() {
            @Override
            public void acceptSearchMatch(SearchMatch match) throws CoreException {

                // We get the Java type that is annotated with @WebService
                if (match.getElement() instanceof IType) {
                    IType type = (IType) match.getElement();
                    ICompilationUnit cu = type.getCompilationUnit();
                    if (cu != null && cu.getCorrespondingResource() != null)
                        cu.getCorrespondingResource().delete(true, monitor);
                    else
                        PetalsCommonPlugin.log("A WS client could not be deleted (no compilation unit).",
                                IStatus.ERROR);
                }
            }
        };

        new SearchEngine().search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                SearchEngine.createJavaSearchScope(new IJavaElement[] { jp }, false), requestor, monitor);
    }

    /**
     * Creates JAX-WS implementations from annotated interfaces contained in a Java project.
     * <p>
     * For very interface annotated with @WebService, a default implementing class
     * is created and replaces the interface.
     * </p>
     *
     * @param jp the containing Java project
     * @param monitor the progress monitor
     * @return a map associating a WSDL service name with a JAX-WS class
     * <p>
     * Key = a WSDL service name<br />
     * Value = the JAX-WS class
     * </p>
     *
     * @throws CoreException if the annotated interfaces could not be listed
     */
    public static Map<String, String> createJaxWsImplementation(IJavaProject jp, final IProgressMonitor monitor)
            throws CoreException {

        Map<String, String> serviceNameToImplName = new HashMap<String, String>();
        Map<String, String> classNameToServiceName = getJaxAnnotatedJavaTypes(jp, monitor, false, true);
        for (Map.Entry<String, String> entry : classNameToServiceName.entrySet()) {

            // Get the class source
            String className = entry.getKey();
            IType type = jp.findType(className);
            if (type == null || type.getCompilationUnit() == null)
                continue;

            // Get the interface's simple name
            int index = className.lastIndexOf('.');
            String simpleName;
            if (index == -1)
                simpleName = className;
            else
                simpleName = className.substring(index + 1);

            String implName = simpleName.replaceFirst("PortType$", "");
            if (implName.equals(simpleName))
                implName = simpleName + "Impl";

            serviceNameToImplName.put(entry.getValue(), className.replaceFirst(simpleName + "$", implName));
            StringBuffer sourceBuffer = replaceInterfaceDeclarationByImpl(type.getCompilationUnit().getSource(),
                    simpleName, implName);
            String source = replaceInterfaceMethodsByImpl(sourceBuffer);

            // Create the class file and set its content
            ICompilationUnit cu = type.getCompilationUnit();
            if (cu != null && cu.getCorrespondingResource() != null) {
                IFile implFile = cu.getCorrespondingResource().getParent().getFile(new Path(implName + ".java"));

                // Normally, the file should exist and be the interface's one
                if (!implFile.exists())
                    implFile.create(new ByteArrayInputStream(source.getBytes()), true, monitor);
                else
                    implFile.setContents(new ByteArrayInputStream(source.getBytes()), true, true, monitor);
            } else
                PetalsCommonPlugin.log("The JAX-WS implementation could not be created (no compilation unit).",
                        IStatus.ERROR);
        }

        return serviceNameToImplName;
    }

    /**
     * Replaces the interface declaration by the declaration of an implementation.
     * @param interfaceDecl
     * @param simpleName
     * @param implName
     * @return
     */
    static StringBuffer replaceInterfaceDeclarationByImpl(String interfaceDecl, String simpleName,
            String implName) {

        StringBuffer source = new StringBuffer(interfaceDecl);
        Pattern interfacePattern = Pattern.compile(" interface\\s+\\w+\\s*\\{", Pattern.MULTILINE | Pattern.DOTALL);

        Matcher matcher = interfacePattern.matcher(source);
        if (matcher.find()) {
            source = source.replace(matcher.start(), matcher.end(),
                    " class " + implName + " implements " + simpleName + " {");
        }

        return source;
    }

    /**
     * Replaces interface methods by their implementations.
     * <p>
     * Made public for test purposes.
     * Not really useful otherwise.
     * </p>
     *
     * @param source
     * @return the modified source
     */
    public static String replaceInterfaceMethodsByImpl(StringBuffer source) {

        // Get the methods' default implementations
        Pattern methodPattern = Pattern.compile("public\\s+(\\w+)\\s+\\w+\\s*\\([^;]*\\)\\s*(throws\\s+\\w+)?\\s*;",
                Pattern.MULTILINE | Pattern.DOTALL);

        Matcher matcher = methodPattern.matcher(source);
        while (matcher.find()) {

            String javaType = matcher.group(1);
            String defaultImpl = getDefaultImplementation(javaType);
            String methodDecl = matcher.group(0);

            methodDecl = methodDecl.substring(0, methodDecl.length() - 1).trim(); // Remove the semicolon
            String methodRepl = methodDecl + " {\n\t\t" + defaultImpl + "\n\t}";
            source = source.replace(matcher.start(), matcher.end(), methodRepl);

            // We progressively modify the searched string.
            // We have to reset the matcher to make sure it does not stop to
            // an offset that has now been pushed by our additions.
            matcher.reset();
        }

        return source.toString();
    }

    /**
     * Gets the default implementation text for a given return type.
     * @param javaType the Java type (void, primitive type or class name)
     * @return the default implementation (never null)
     */
    static String getDefaultImplementation(String javaType) {

        String result;
        if ("void".equals(javaType))
            result = "// nothing";
        else if ("int".equals(javaType))
            result = "return 0;";
        else if ("boolean".equals(javaType))
            result = "return false;";
        else if ("long".equals(javaType))
            result = "return 0;";
        else if ("double".equals(javaType))
            result = "return 0;";
        else if ("float".equals(javaType))
            result = "return 0;";
        else if ("byte".equals(javaType))
            result = "return 0;";
        else if ("short".equals(javaType))
            result = "return 0;";
        else if ("char".equals(javaType))
            result = "return ' ';";
        else
            result = "return null;";

        return result;
    }

    /**
     * An exception related to this utility class.
     */
    public static class JaxWsException extends Exception {

        /**
         * The serial ID.
         */
        private static final long serialVersionUID = -358390664739689755L;

        /**
         * Constructor.
         */
        public JaxWsException() {
            super();
        }

        /**
         * Constructor.
         * @param message
         * @param cause
         */
        public JaxWsException(String message, Throwable cause) {
            super(message, cause);
        }

        /**
         * Constructor.
         * @param message
         */
        public JaxWsException(String message) {
            super(message);
        }

        /**
         * Constructor.
         * @param cause
         */
        public JaxWsException(Throwable cause) {
            super(cause);
        }
    }
}