blob: 55861e1b9c00310b7d2b5972cf8df89cb331c392 [file] [log] [blame]
/*
* 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
*/
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
*/
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
*/
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[]{};
}
}