| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.tools.weaving.jpa; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Writer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.Iterator; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarOutputStream; |
| |
| import org.eclipse.persistence.exceptions.StaticWeaveException; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.jpa.deployment.ArchiveFactoryImpl; |
| import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor; |
| import org.eclipse.persistence.internal.jpa.weaving.AbstractStaticWeaveOutputHandler; |
| import org.eclipse.persistence.internal.jpa.weaving.StaticWeaveDirectoryOutputHandler; |
| import org.eclipse.persistence.internal.jpa.weaving.StaticWeaveJAROutputHandler; |
| import org.eclipse.persistence.internal.localization.ToStringLocalization; |
| import org.eclipse.persistence.jpa.Archive; |
| import org.eclipse.persistence.logging.AbstractSessionLog; |
| import org.eclipse.persistence.logging.SessionLog; |
| |
| /** |
| * <p> |
| * <b>Description</b>: The StaticWeaveProcessor controls the static weaving process. It is invoked by both the command line |
| * StaticWeave class and the StaticWeaveAntTask. |
| * <p> |
| * <b>Responsibilities</b>: Process the source classes, performs weaving as necessary out outputs to the target |
| */ |
| @SuppressWarnings("deprecation") |
| public class StaticWeaveProcessor { |
| private URL source; |
| private URL target; |
| private URL persistenceInfo; |
| private String persistenceXMLLocation; |
| private Writer logWriter; |
| private ClassLoader classLoader; |
| private int logLevel = SessionLog.OFF; |
| |
| private static final int NUMBER_OF_BYTES = 1024; |
| |
| /** |
| * Constructs an instance of StaticWeaveProcessor |
| * @param source the name of the location to be weaved |
| * @param target the name of the location to be weaved to |
| * @throws MalformedURLException |
| */ |
| public StaticWeaveProcessor(String source, String target)throws MalformedURLException{ |
| if (source != null) { |
| this.source=new File(source).toURL(); |
| } |
| if (target != null) { |
| this.target=new File(target).toURL(); |
| } |
| } |
| |
| /** |
| * Constructs an instance of StaticWeaveProcessor |
| * @param source the File object of the source to be weaved |
| * @param target the File object of the target to be weaved to |
| * @throws MalformedURLException |
| */ |
| public StaticWeaveProcessor(File source, File target)throws MalformedURLException { |
| this.source=source.toURL(); |
| this.target=target.toURL(); |
| } |
| |
| /** |
| * Constructs an instance of StaticWeaveProcessor |
| * @param source the URL of the source to be weaved |
| * @param target the URL of the target to be weaved to |
| */ |
| public StaticWeaveProcessor(URL source, URL target){ |
| this.source = source; |
| this.target = target; |
| } |
| |
| /** |
| * The method allows user to specify the output for the log message. |
| * @param logWriter the location where the log message writes to. the default value is standard out |
| */ |
| public void setLog(Writer logWriter){ |
| this.logWriter = logWriter; |
| } |
| |
| /** |
| * The method allows user to define nine levels EclipseLink logging. |
| * @param level - the integer value of log level. default is OFF. |
| */ |
| public void setLogLevel(int level){ |
| this.logLevel = level; |
| } |
| |
| /** |
| * Set the user classloader. |
| */ |
| public void setClassLoader(ClassLoader classLoader){ |
| this.classLoader=classLoader; |
| } |
| |
| /** |
| * Set an explicitly identified URL of the location containing persistence.xml. |
| * @param persistenceInfo the URL of the location containing persistence.xml, the URL |
| * must point to the root of META-INF/persistence.xml |
| */ |
| public void setPersistenceInfo(URL persistenceInfo){ |
| this.persistenceInfo = persistenceInfo; |
| } |
| |
| /** |
| * Set an explicitly identified the location containing persistence.xml. |
| * @param persistenceInfoPath the path of the location containing persistence.xml, the path |
| * must point to the root of META-INF/persistence.xml |
| */ |
| public void setPersistenceInfo(String persistenceInfoPath) throws MalformedURLException{ |
| if (persistenceInfoPath != null){ |
| this.persistenceInfo = new File(persistenceInfoPath).toURL(); |
| } |
| } |
| |
| public String getPersistenceXMLLocation() { |
| return persistenceXMLLocation; |
| } |
| |
| /** |
| * Set a specific location to look for persistence.xml |
| * by default we will look in META-INF/persistence.xml |
| * @param persistenceXMLLocation |
| */ |
| public void setPersistenceXMLLocation(String persistenceXMLLocation) { |
| this.persistenceXMLLocation = persistenceXMLLocation; |
| } |
| |
| /** |
| * Set an explicitly identified the location containing persistence.xml. |
| * @param persistenceInfoFile the file containing persistence.xml, the file |
| * should contain META-INF/persistence.xml |
| */ |
| public void setPersistenceInfo(File persistenceInfoFile) throws MalformedURLException{ |
| if (persistenceInfoFile!=null){ |
| this.persistenceInfo=persistenceInfoFile.toURL(); |
| } |
| } |
| |
| |
| /** |
| * This method performs weaving function on the class individually from the specified source. |
| */ |
| public void performWeaving() throws URISyntaxException,MalformedURLException,IOException{ |
| preProcess(); |
| process(); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method perform all necessary steps(verification, pre-build the target directory) |
| * prior to the invocation of the weaving function. |
| */ |
| private void preProcess() throws URISyntaxException,MalformedURLException{ |
| //Instantiate default session log |
| AbstractSessionLog.getLog().setLevel(this.logLevel); |
| if(logWriter!=null){ |
| AbstractSessionLog.getLog().setWriter(logWriter); |
| } |
| |
| //Make sure the source is existing |
| if(!(new File(Helper.toURI(source)).exists())){ |
| throw StaticWeaveException.missingSource(); |
| } |
| |
| URI targetURI = Helper.toURI(target); |
| //Verification target and source, two use cases create warning or exception. |
| //1. If source is directory and target is jar - |
| // This will lead unknown outcome, user attempt to use this tool to pack outcome into a Jar. |
| // Warning message will be logged, this is can be worked around by other utilities. |
| //2. Both source and target are specified as a same jar - |
| // User was trying to perform weaving in same Jar which is not supported, an Exception will be thrown. |
| if(isDirectory(source) && targetURI.toString().endsWith(".jar")){ |
| AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.WEAVER, ToStringLocalization.buildMessage("staticweave_processor_unknown_outcome", new Object[]{null}), null, false); |
| } |
| |
| if(!isDirectory(source) && target.toString().equals(source.toString())){ |
| throw StaticWeaveException.weaveInplaceForJar(source.toString()); |
| } |
| |
| //pre-create target if it is directory and dose not exist. |
| //Using the method File.isDirectory() is not enough to determine what the type(dir or jar) |
| //of the target(specified by URL)that user want to create. File.isDirectory() will return false in |
| //two possibilities, the location either is not directory or the location dose not exist. |
| //Therefore pre-build of the directory target is required. Pre-build for the file(JAR) target |
| //is not required since it gets built automatically by opening outputstream. |
| File targetDir = new File(targetURI); |
| if(!targetDir.exists()){ |
| if(!targetURI.toString().endsWith(".jar")){ |
| if (!targetDir.mkdirs()) { |
| AbstractSessionLog.getLog().log(AbstractSessionLog.FINE, AbstractSessionLog.WEAVER, |
| ToStringLocalization.buildMessage("staticweave_processor_dir_not_created", new Object[]{targetDir})); |
| } |
| //if directory fails to build, which may leads to unknown outcome since it will |
| //be treated as single file in the class StaticWeaveHandler and automatically gets built |
| //by outputstream. |
| |
| //re-assign URL. |
| target = targetDir.toURL(); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The method performs weaving function |
| */ |
| private void process() throws IOException,URISyntaxException{ |
| // Instantiate output handler. |
| AbstractStaticWeaveOutputHandler swoh; |
| if (isDirectory(this.target)) { |
| swoh= new StaticWeaveDirectoryOutputHandler(this.source,this.target); |
| }else{ |
| swoh= new StaticWeaveJAROutputHandler(new JarOutputStream(new FileOutputStream(new File(Helper.toURI(this.target))))); |
| } |
| |
| // Instantiate classloader. |
| this.classLoader = (this.classLoader == null)? Thread.currentThread().getContextClassLoader():this.classLoader; |
| this.classLoader = new URLClassLoader(getURLs(), this.classLoader); |
| |
| // Instantiate the classtransformer, we check if the persistenceinfo URL has been specified. |
| StaticWeaveClassTransformer classTransformer=null; |
| if (persistenceInfo!=null) { |
| classTransformer = new StaticWeaveClassTransformer(persistenceInfo, persistenceXMLLocation, this.classLoader,this.logWriter,this.logLevel); |
| } else{ |
| classTransformer = new StaticWeaveClassTransformer(source, persistenceXMLLocation, this.classLoader,this.logWriter,this.logLevel); |
| } |
| |
| // Starting process. |
| Archive sourceArchive =(new ArchiveFactoryImpl()).createArchive(source, null, null); |
| if (sourceArchive != null) { |
| try { |
| Iterator<String> entries = sourceArchive.getEntries(); |
| while (entries.hasNext()){ |
| String entryName = entries.next(); |
| InputStream entryInputStream = sourceArchive.getEntry(entryName); |
| |
| // Add a directory entry |
| swoh.addDirEntry(getDirectoryFromEntryName(entryName)); |
| |
| // Add a regular entry |
| JarEntry newEntry = new JarEntry(entryName); |
| |
| // Ignore non-class files. |
| if (!entryName.endsWith(".class") || "module-info.class".equals(entryName)) { |
| swoh.addEntry(entryInputStream, newEntry); |
| continue; |
| } |
| |
| String className = PersistenceUnitProcessor.buildClassNameFromEntryString(entryName) ; |
| |
| byte[] originalClassBytes=null; |
| byte[] transferredClassBytes=null; |
| try { |
| Class<?> thisClass = this.classLoader.loadClass(className); |
| // If the class is not in the classpath, we simply copy the entry |
| // to the target(no weaving). |
| if (thisClass == null){ |
| swoh.addEntry(entryInputStream, newEntry); |
| continue; |
| } |
| |
| // Try to read the loaded class bytes, the class bytes is required for |
| // classtransformer to perform transfer. Simply copy entry to the target(no weaving) |
| // if the class bytes can't be read. |
| InputStream is = this.classLoader.getResourceAsStream(entryName); |
| if (is!=null){ |
| ByteArrayOutputStream baos = null; |
| try{ |
| baos = new ByteArrayOutputStream(); |
| byte[] bytes = new byte[NUMBER_OF_BYTES]; |
| int bytesRead = is.read(bytes, 0, NUMBER_OF_BYTES); |
| while (bytesRead >= 0){ |
| baos.write(bytes, 0, bytesRead); |
| bytesRead = is.read(bytes, 0, NUMBER_OF_BYTES); |
| } |
| originalClassBytes = baos.toByteArray(); |
| } finally { |
| baos.close(); |
| is.close(); |
| } |
| } else { |
| swoh.addEntry(entryInputStream, newEntry); |
| continue; |
| } |
| |
| // If everything is OK so far, we perform the weaving. we need three parameters in order to |
| // class to perform weaving for that class, the class name,the class object and class bytes. |
| transferredClassBytes = classTransformer.transform(className.replace('.', '/'), thisClass, originalClassBytes); |
| |
| // If transferredClassBytes is null means the class dose not get woven. |
| if (transferredClassBytes!=null){ |
| swoh.addEntry(newEntry, transferredClassBytes); |
| } else { |
| swoh.addEntry(entryInputStream, newEntry); |
| } |
| } catch (IllegalClassFormatException e) { |
| AbstractSessionLog.getLog().logThrowable(AbstractSessionLog.WARNING, AbstractSessionLog.WEAVER, e); |
| // Anything went wrong, we need log a warning message, copy the entry to the target and |
| // process next entry. |
| swoh.addEntry(entryInputStream, newEntry); |
| continue; |
| } catch (ClassNotFoundException e) { |
| AbstractSessionLog.getLog().logThrowable(AbstractSessionLog.WARNING, AbstractSessionLog.WEAVER, e); |
| swoh.addEntry(entryInputStream, newEntry); |
| continue; |
| } finally { |
| // Need close the inputstream for current entry before processing next one. |
| entryInputStream.close(); |
| } |
| } |
| } finally { |
| sourceArchive.close(); |
| swoh.closeOutputStream(); |
| } |
| } |
| } |
| |
| //Extract directory from entry name. |
| public static String getDirectoryFromEntryName(String entryName){ |
| String result=""; |
| if (entryName==null ) { |
| return result; |
| } |
| if(entryName.lastIndexOf('/')>=0){ |
| result=entryName.substring(0, entryName.lastIndexOf('/'))+File.separator; |
| } |
| return result; |
| } |
| |
| /** |
| * Determine whether or not the URL is pointing to directory. |
| */ |
| private boolean isDirectory(URL url) throws URISyntaxException{ |
| File file = new File(Helper.toURI(url)); |
| if (file.isDirectory()) { |
| return true; |
| }else{ |
| return false; |
| } |
| } |
| |
| /** |
| * Generate URL array for specified source and persistenceinfo. |
| */ |
| private URL[] getURLs(){ |
| if((this.source!=null) && (this.persistenceInfo!=null)){ |
| return new URL[]{this.persistenceInfo,this.source}; |
| } else if(this.source!=null){ |
| return new URL[]{this.source}; |
| } else if (this.persistenceInfo!=null){ |
| return new URL[]{this.persistenceInfo}; |
| } |
| return new URL[]{}; |
| } |
| } |