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