blob: afb741564d24a49f5fa38e3fb10d46c41b83b8a6 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 IBM Corporation. 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:
// dclarke, mnorman - Dynamic Persistence
// http://wiki.eclipse.org/EclipseLink/Development/Dynamic
// (https://bugs.eclipse.org/bugs/show_bug.cgi?id=200045)
// 08/29/2016 Jody Grassel
// - 500441: Eclipselink core has System.getProperty() calls that are not potentially executed under doPriv()
//
package org.eclipse.persistence.dynamic;
//javase imports
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.config.SystemProperties;
import org.eclipse.persistence.exceptions.DynamicException;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.sessions.Session;
/**
* This custom ClassLoader provides support for dynamically generating classes
* within an EclipseLink application using byte codes created using a
* {@link DynamicClassWriter}. A DynamicClassLoader requires a parent or
* delegate class-loader which is provided to the constructor. This delegate
* class loader handles the lookup and storage of all created classes.
*
* @author dclarke, mnorman
* @since EclipseLink 1.2
*/
public class DynamicClassLoader extends ClassLoader {
/**
* Map of {@link DynamicClassWriter} used to dynamically create a class in
* the {@link #findClass(String)} call. The application must register
* classes using addClass or createDynameClass prior to the
* {@link #findClass(String)} being invoked.
* <p>
* The map of writers is maintained for the life of this DynamicClassLoader
* instance to ensure additional requests to create dynamic classes of the
* same name are properly verified. Duplicate requests for dynamic classes
* of the same name, same writer type, and the same parent class are
* permitted but different parent classes or different writer types are not.
*/
protected Map<String, EclipseLinkClassWriter> classWriters = new HashMap<>();
protected Map<String, EnumInfo> enumInfoRegistry = new HashMap<>();
/**
* Default writer to use if one is not specified.
*/
public DynamicClassWriter defaultWriter = new DynamicClassWriter();
/**
* Create a DynamicClassLoader providing the delegate loader and leaving the
* defaultWriter as {@link DynamicClassWriter}
*/
public DynamicClassLoader(ClassLoader delegate) {
super(delegate);
}
/**
* Create a DynamicClassLoader providing the delegate loader and a default
* {@link DynamicClassWriter}.
*/
public DynamicClassLoader(ClassLoader delegate, DynamicClassWriter writer) {
this(delegate);
this.defaultWriter = writer;
}
public DynamicClassWriter getDefaultWriter() {
return this.defaultWriter;
}
protected Map<String, EclipseLinkClassWriter> getClassWriters() {
return this.classWriters;
}
public EclipseLinkClassWriter getClassWriter(String className) {
return getClassWriters().get(className);
}
public void addEnum(String className, Object... literalLabels) {
EnumInfo enumInfo = enumInfoRegistry.get(className);
if (enumInfo == null) {
enumInfo = new EnumInfo(className);
enumInfoRegistry.put(className, enumInfo);
}
if (literalLabels != null) {
for (Object literalLabel : literalLabels) {
if (literalLabel != null) {
enumInfo.addLiteralLabel(literalLabel.toString());
}
}
}
addClass(className);
}
/**
* Register a class to be dynamically created using the default
* {@link DynamicClassWriter}.
*
* @see #addClass(String, EclipseLinkClassWriter)
*/
public void addClass(String className) {
addClass(className, getDefaultWriter());
}
/**
* Register a class to be dynamically created using a copy of default
* {@link DynamicClassWriter} but specifying a different parent class.
*
* @see #addClass(String, EclipseLinkClassWriter)
*/
public void addClass(String className, Class<?> parentClass) {
addClass(className, getDefaultWriter().createCopy(parentClass));
}
/**
* Register a class to be dynamically created using the provided
* {@link DynamicClassWriter}. The registered writer is used when the
* {@link #findClass(String)} method is called back on this loader from the
* {@link #loadClass(String)} call.
* <p>
* If a duplicate request is made for the same className and the writers are
* not compatible a {@link DynamicException} will be thrown. If the
* duplicate request contains a compatible writer then the second request is
* ignored as the class may already have been generated.
*
* @see #findClass(String)
*/
public void addClass(String className, EclipseLinkClassWriter writer) throws DynamicException {
EclipseLinkClassWriter existingWriter = getClassWriter(className);
// Verify that the existing writer is compatible with the requested
if (existingWriter != null) {
if (!existingWriter.isCompatible(writer)) {
throw DynamicException.incompatibleDuplicateWriters(className, existingWriter, writer);
}
} else {
getClassWriters().put(className, writer == null ? getDefaultWriter() : writer);
}
}
/**
* Create a dynamic class registering a writer and then forcing the provided
* class name to be loaded.
*
*/
public Class<?> createDynamicClass(String className, DynamicClassWriter writer) {
addClass(className, writer);
Class<?> newDynamicClass = null;
try {
newDynamicClass = loadClass(className);
}
catch (ClassNotFoundException cnfe) {
throw new IllegalArgumentException("DyanmicClassLoader: could not create class " + className);
}
return checkAssignable(newDynamicClass);
}
protected Class<?> checkAssignable(Class<?> clz) {
EclipseLinkClassWriter assignedClassWriter = getClassWriters().get(clz.getName());
if ((assignedClassWriter.getParentClass() == null && !assignedClassWriter.getParentClassName().equals(clz.getName())) || !assignedClassWriter.getParentClass().isAssignableFrom(clz)) {
throw new IllegalArgumentException("DynamicClassLoader: " + clz.getName() + " not compatible with parent class " + assignedClassWriter.getParentClass().getName());
}
return clz;
}
/**
* Create a new dynamic entity type for the specified name assuming the use
* of the default writer and its default parent class.
*
* @see #createDynamicClass(String, DynamicClassWriter)
*/
public Class<?> createDynamicClass(String className) {
return createDynamicClass(className, getDefaultWriter());
}
/**
* Create a new dynamic entity type for the specified name with the
* specified parent class.
*
* @see #createDynamicClass(String, DynamicClassWriter)
*/
public Class<?> createDynamicClass(String className, Class<?> parentClass) {
return createDynamicClass(className, new DynamicClassWriter(parentClass));
}
/**
* Create an adapter for given {@code className}
*
*/
public void createDynamicAdapter(String className) {
// default no-op
}
/**
* Create a collection adapter for given {@code className}
*
*/
public void createDynamicCollectionAdapter(String className) {
// default no-op
}
/**
* Create a reference for given {@code className}
*
*/
public void createDynamicReferenceAdapter(String className) {
// default no-op
}
/**
* Create a new dynamic class if a ClassWriter is registered for the
* provided className. This code is single threaded to ensure only one class
* is created for a given name and that the ClassWriter is removed
* afterwards.
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
EclipseLinkClassWriter writer = getClassWriter(className);
if (writer != null) {
try {
byte[] bytes = writer.writeClass(this, className);
if (bytes != null) {
String outputPath = PrivilegedAccessHelper.getSystemProperty(SystemProperties.WEAVING_OUTPUT_PATH, "");
if (!outputPath.equals("")) {
Helper.outputClassFile(className, bytes, outputPath);
}
}
return defineDynamicClass(className, bytes);
} catch (ClassFormatError cfe) {
throw new ClassNotFoundException(className, cfe);
} catch (ClassCircularityError cce) {
throw new ClassNotFoundException(className, cce);
}
}
return super.findClass(className);
}
/**
* Converts an array of bytes into an instance of class <code>Class</code>.
* Before the <code>Class</code> can be used it must be resolved.
*
*/
protected Class<?> defineDynamicClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
/**
* Lookup the DynamicConversionManager for the given session. If the
* existing ConversionManager is not an instance of DynamicConversionManager
* then create a new one and replace the existing one.
*
*/
public static DynamicClassLoader lookup(Session session) {
ConversionManager cm = null;
if (session == null) {
cm = ConversionManager.getDefaultManager();
} else {
cm = session.getPlatform().getConversionManager();
}
if (cm.getLoader() instanceof DynamicClassLoader) {
return (DynamicClassLoader) cm.getLoader();
}
DynamicClassLoader dcl = new DynamicClassLoader(cm.getLoader());
cm.setLoader(dcl);
if (session == null) {
ConversionManager.setDefaultLoader(dcl);
}
return dcl;
}
public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<>();
public EnumInfo(String className) {
this.className = className;
}
public String getClassName() {
return className;
}
public String[] getLiteralLabels() {
return literalLabels.toArray(new String[literalLabels.size()]);
}
public void addLiteralLabel(String literalLabel) {
if (!literalLabels.contains(literalLabel) && literalLabel != null) {
literalLabels.add(literalLabel);
}
}
}
}