Java tutorial
/* Copyright (c) 2011-2014 Richard Gay <gay@mais.informatik.tu-darmstadt.de> * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.cliseau.composer.javatarget; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.concurrent.Callable; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.antlr.stringtemplate.*; import org.antlr.stringtemplate.language.*; import org.apache.commons.lang.StringUtils; import net.cliseau.composer.UnitGenerationException; import net.cliseau.composer.PlainInetAddress; import net.cliseau.composer.javatarget.PointcutParser.ParseException; import net.cliseau.composer.javatarget.PointcutParser.PointcutParser; import net.cliseau.composer.javatarget.PointcutParser.Pointcut; import net.cliseau.composer.javatarget.PointcutParser.PointcutSpec; import net.cliseau.composer.config.base.InvalidConfigurationException; import net.cliseau.composer.config.target.AspectJConfig; import net.cliseau.utils.CommandRunner; /** * Exception during unit generation caused by a pointcut parsing failure. * * Objects of this class should be thrown during unit generation, when the * pointcut parser could not handle the specified pointcut file. * * @see net.cliseau.composer.javatarget.PointcutParser.PointcutParser */ class PointcutParseException extends UnitGenerationException { /** * Construct a PointcutParseException object. * * @param message The message explaining this exception. */ public PointcutParseException(final String message) { super(message); } } /** * Class for generating and weaving CliSeAu aspects from pointcut definitions. * * An AspectWeaver object generates AOP aspects from a pointcut definition * and two templates for aspects and advices. For working with the templates are * stored in files and processed with the help of the StringTemplate library. * The generated aspects are woven into a specified Java program using AspectJ. * * The aspects generated by this class represent the Interceptor and Enforcer * components in the conceptual model of Service Automata. */ public class AspectWeaver implements Callable<Void> { /** The directory into which the modified target is to be written. */ private final String destinationDirectory; /** The configuration of the target. */ private final AspectJConfig aspectjConfig; /** Address under which the interceptor part of the aspect can contact the local coordinator. */ private final PlainInetAddress coordinatorAddress; /** Address under which the enforcer part of the aspect is supposed to listen to decisions from the local coordinator. */ private final PlainInetAddress enforcerAddress; /** Collection of dependencies that are introduced by the woven aspect (relative to the target program path). */ private final Collection<String> inlinedDependencies; /** Enable/disable verbose information. */ private final boolean verbose; /** Enable/disable measurements inside the advice. */ private final boolean measureAdviceTime; /** Name of the aspect that realizes a CliSeAu unit */ private static final String AspectName = "CliSeAu"; /** * Source version for the AspectJ aspect. * * This version depends on the features that the advice code makes use of. * Currently, we need at least version 1.5 in order to let the advice make * use of Java's generics (which might be used by the Enforcer class). */ private static final String aspectJSourceVersion = "1.5"; /** String for separating class paths in manifest files */ private static final String manifestClassPathSeparator = " "; // not ":"! /** * Construct AspectWeaver object. * * @param destinationDirectory The directory into which the modified target is to be written. * @param aspectjConfig The configuration of the target. * @param coordinatorAddress Address under which the interceptor part of the aspect can contact the local coordinator. * @param enforcerAddress Address under which the enforcer part of the aspect is supposed to listen to decisions from the local coordinator. * @param inlinedDependencies Collection of dependencies that are introduced by the woven aspect (relative to the target program path). * @param measureAdviceTime Enable/disable measurements inside the advice. * @param verbose Enable/disable verbose information. */ public AspectWeaver(final String destinationDirectory, final AspectJConfig aspectjConfig, final PlainInetAddress coordinatorAddress, final PlainInetAddress enforcerAddress, final Collection<String> inlinedDependencies, final boolean measureAdviceTime, final boolean verbose) throws InvalidConfigurationException { this.destinationDirectory = destinationDirectory; this.aspectjConfig = aspectjConfig; this.coordinatorAddress = coordinatorAddress; this.enforcerAddress = enforcerAddress; this.inlinedDependencies = inlinedDependencies; this.measureAdviceTime = measureAdviceTime; this.verbose = verbose; } /** * Create aspect and weave it into the target program. * * @return Void Nothing. * @exception InstrumentationException Thrown when instrumenting the target program fails. */ public Void call() throws IOException, InstrumentationException, PointcutParseException, InvalidConfigurationException { File aspectFile = new File(getDestinationDirectory() + File.separator + AspectName + ".aj"); File targetJARFile = new File(getDestinationDirectory() + File.separator + (new File(aspectjConfig.getJavaProgramJAR())).getName()); generateAspect(aspectFile); weaveAspect(aspectFile, targetJARFile); aspectFile.delete(); updateTargetManifest(targetJARFile); return null; } /** * Get the boxed type name for a given type name. * * All object types are unchanged; only primitive types such as "int", "char" * etc. are transformed to their boxed variants such as "Integer", "Char", * etc. Such a non-primitive type may be required when the type is used for * Java's generics. * * @param type Type name * @return Object type name corresponding to the given type */ private static String typeToBoxedType(final String type) { if (type.equals("boolean")) return "Boolean"; else if (type.equals("byte")) return "Byte"; else if (type.equals("char")) return "Char"; else if (type.equals("double")) return "Double"; else if (type.equals("float")) return "Float"; else if (type.equals("int")) return "Integer"; else if (type.equals("long")) return "Long"; else if (type.equals("short")) return "Short"; else if (type.equals("void")) return "Void"; // yes, java.lang.Void exists else return type; // "type" is already an object (all primitive types are listed above) } /** * Generate an aspect which realizes interceptor and enforcer of a CliSeAu unit. * * Using the aspect and advice template files, this method generates a * CliSeAu unit's aspect. * * @param destinationFile Name of file to write the aspect to. */ private void generateAspect(final File destinationFile) throws IOException, PointcutParseException, InvalidConfigurationException { StringTemplateGroup group = new StringTemplateGroup("SAgroup", AngleBracketTemplateLexer.class); // Parse the pointcuts, extracting all information required // for the generation of our advice // (note: the FileReader may throw a FileNotFoundException) PointcutParser parser = new PointcutParser( new FileReader(aspectjConfig.getInstrumentationPointcutSpecFile())); PointcutSpec pspec; try { pspec = parser.PointcutSpecification(); } catch (ParseException e) { // This happens when the aspect generator fails to parse the pointcut // specification. //TODO: add some more information to the message of the exception! throw new PointcutParseException(e.getMessage()); } String advice_list = ""; String pointcuts = ""; for (Pointcut pc : pspec.getPointcuts()) { StringTemplate advice = group.getInstanceOf(aspectjConfig.getAspectJAdviceTemplate()); advice.setAttribute("PointcutName", pc.getName()); advice.setAttribute("TypedParameters", pc.getTypedParametersList()); advice.setAttribute("Parameters", pc.getParametersList()); String returnType = pc.getReturnType(); advice.setAttribute("ReturnType", returnType); advice.setAttribute("ObjReturnType", typeToBoxedType(returnType)); advice.setAttribute("hasReturnType", new Boolean(!returnType.equals(Pointcut.noReturnType))); advice.setAttribute("CriticalEventFactory", aspectjConfig.getCriticalEventFactoryName()); advice.setAttribute("EnforcerFactory", aspectjConfig.getEnforcerFactoryName()); advice.setAttribute("measureTime", new Boolean(measureAdviceTime)); advice_list = advice_list + "\n" + advice.toString(); // In order to avoid that the CliSeAu unit's interceptor itself is modified, // we add the restriction that modifications must not happen in the automaton aspects pc.setPointcutExpression("(" + pc.getPointcutExpression() + ") && !within(" + AspectName + ")"); pointcuts += pc.toString(); } String import_list = ""; for (String imp : pspec.getImports()) { import_list += "import " + imp + ";\n"; } StringTemplate aspect = group.getInstanceOf(aspectjConfig.getAspectJAspectTemplate()); aspect.setAttribute("AspectName", AspectName); aspect.setAttribute("Pointcuts", pointcuts); aspect.setAttribute("Advice", advice_list); aspect.setAttribute("Imports", import_list); setAspectProperties(aspect); // write the aspect to the given file (may throw IOException) PrintWriter aspectWriter = new PrintWriter(new FileWriter(destinationFile, false)); aspectWriter.print(aspect.toString()); aspectWriter.close(); } /** * Set aspect properties beyond the advice, imports and aspect name. * * This method particularly allows easily extending AspectWeaver * to use further aspect properties with modified aspect templates. */ protected void setAspectProperties(StringTemplate aspect) { aspect.setAttribute("CoordAddr", getCoordinatorAddress().toCreationCode()); aspect.setAttribute("EnfSocket", getEnforcerAddress().toServerCreationCode()); } /** * Weave a given aspect into a given target program using AspectJ. * * @param aspectFile File containing the aspect to weave. * @param targetJARFile File to write the resulting JAR into. * @exception InstrumentationException Thrown when instrumenting the target program fails. */ private void weaveAspect(final File aspectFile, final File targetJARFile) throws IOException, InstrumentationException, InvalidConfigurationException { ArrayList<String> instrumentParams = new ArrayList<String>( Arrays.asList(aspectjConfig.getAspectJExecutable(), "-classpath", StringUtils.join(getAbsoluteInlinedDependencies(), File.pathSeparator), "-injars", aspectjConfig.getJavaProgramJAR(), "-outjar", targetJARFile.getPath(), "-source", aspectJSourceVersion)); if (verbose) instrumentParams.add("-showWeaveInfo"); instrumentParams.add(aspectFile.getPath()); String[] instrParamsArray = new String[instrumentParams.size()]; instrumentParams.toArray(instrParamsArray); // note: the "exec" below may throw an IOException if (CommandRunner.exec(instrParamsArray) != CommandRunner.NORMAL_TERMINATION) { throw new InstrumentationException("Could not instrument the target."); } } /** * Update the manifest of a given JAR file to include CliSeAu's dependencies in the classpath list. * * This method modifies the "Class-Path" entry of the given JAR file's * manifest to include the paths of all runtime dependencies that are caused * by the instrumentation with the CliSeAu unit. * * @param targetJARFile The JAR file whose manifest to update. * @exception IOException Thrown when reading or writing the JAR file fails. * @todo Check whether this update is possible also with the JarFile API alone. */ private void updateTargetManifest(final File targetJARFile) throws IOException, InvalidConfigurationException { // Step 1: Obtain the existing class path list from the target JAR file JarFile targetJAR = new JarFile(targetJARFile); Manifest targetManifest = targetJAR.getManifest(); LinkedList<String> classPathEntries; if (targetManifest != null) { String targetClassPath = targetManifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); if (targetClassPath == null) { targetClassPath = ""; } classPathEntries = new LinkedList<String>( Arrays.asList(targetClassPath.split(manifestClassPathSeparator))); } else { classPathEntries = new LinkedList<String>(); } // close the object again (this shall ensure that the command in // Step 4 can safely work on the file again) targetJAR.close(); // Step 2: Add all newly introduced runtime dependencies of CliSeAu classPathEntries.addAll(getInlinedDependencies()); // Step 3: Create a new manifest file with *only* the updated class path directive File manifestUpdate = File.createTempFile("MANIFEST", ".MF"); PrintWriter muWriter = new PrintWriter(manifestUpdate); muWriter.print("Class-path:"); muWriter.print(StringUtils.join(classPathEntries, manifestClassPathSeparator)); muWriter.println(); muWriter.close(); // Step 4: Run "jar" to update the JAR file with the new manifest; this // does not replace the JAR file's manifest with the new one, but // *update* *only* those entries in the JAR file's manifest which are // present in the new manifest. That is, only the class path settings are // updated and everything else remains intact. CommandRunner.exec(new String[] { aspectjConfig.getJarExecutable(), "umf", // update manifest manifestUpdate.getPath(), targetJARFile.getPath() }); // Step 5: cleanup manifestUpdate.delete(); } //TODO: remove all unnecessary getters (i.e., all simple ones??) /** * Get destinationDirectory. * * @return destinationDirectory as String. */ public String getDestinationDirectory() { return destinationDirectory; } /** * Get coordinatorAddress. * * @return coordinatorAddress as PlainInetAddress. */ public PlainInetAddress getCoordinatorAddress() { return coordinatorAddress; } /** * Get enforcerAddress. * * @return enforcerAddress as PlainInetAddress. */ public PlainInetAddress getEnforcerAddress() { return enforcerAddress; } /** * Get verbose. * * @return verbose as boolean. */ public boolean getVerbose() { return verbose; } /** * Get inlinedDependencies. * * @return inlinedDependencies as Collection<String>. */ public Collection<String> getInlinedDependencies() { return inlinedDependencies; } /** * Get absolute version of inlinedDependencies, based on destinationDirectory * * @return Absolute version of inlinedDependencies as Collection<String>. */ public Collection<String> getAbsoluteInlinedDependencies() { ArrayList<String> deps = new ArrayList<String>(inlinedDependencies.size()); for (String dep : inlinedDependencies) { deps.add(destinationDirectory + File.separator + dep); } return deps; } }