Java tutorial
/****************************************************************************** * 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); } } }