All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openjpa.enhance.ManagedClassSubclasser Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.enhance;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.BytecodeWriter;
import org.apache.openjpa.lib.util.JavaVersions;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Files;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.GeneratedClasses;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.UserException;
import serp.bytecode.BCClass;

/**
 * Redefines the method bodies of existing unenhanced classes to make them
 * notify state managers of mutations.
 *
 * @since 1.0.0
 */
public class ManagedClassSubclasser {
    private static final Localizer _loc = Localizer.forPackage(
        ManagedClassSubclasser.class);

    /**
     * For each element in classes, creates and registers a
     * new subclass that implements {@link PersistenceCapable}, and prepares
     * OpenJPA to handle new instances of the unenhanced type. If this is
     * invoked in a Java 6 environment, this method will redefine the methods
     * for each class in the argument list such that field accesses are
     * intercepted in-line. If invoked in a Java 5 environment, this
     * redefinition is not possible; in these contexts, when using field
     * access, OpenJPA will need to do state comparisons to detect any change
     * to any instance at any time, and when using property access, OpenJPA
     * will need to do state comparisons to detect changes to newly inserted
     * instances after a flush has been called.
     *
     * @return the new subclasses, or null if classes
     * is null.
     * @throws UserException if conf requires build-time
     * enhancement and classes includes unenhanced types.
     *
     * @since 1.0.0
     */
    public static List prepareUnenhancedClasses(
        final OpenJPAConfiguration conf,
        final Collection classes,
        final ClassLoader envLoader) {
        if (classes == null)
            return null;
        if (classes.size() == 0)
            return Collections.EMPTY_LIST;

        Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
        if (conf.getRuntimeUnenhancedClassesConstant()
            != RuntimeUnenhancedClasssesModes.SUPPORTED) {
            Collection unenhanced = new ArrayList();
            for (Class cls : classes)
                if (!PersistenceCapable.class.isAssignableFrom(cls))
                    unenhanced.add(cls);
            if (unenhanced.size() > 0) {
                Message msg = _loc.get("runtime-optimization-disabled",
                    unenhanced);
                if (conf.getRuntimeUnenhancedClassesConstant()
                    == RuntimeUnenhancedClasssesModes.WARN)
                    log.warn(msg);
                else
                    throw new UserException(msg);
            }
            return null;
        }

        boolean redefine = ClassRedefiner.canRedefineClasses();
        if (redefine)
            log.info(_loc.get("enhance-and-subclass-and-redef-start",
                classes));
        else
            log.info(_loc.get("enhance-and-subclass-no-redef-start",
                classes));

        final Map map = new HashMap();
        final List subs = new ArrayList(classes.size());
        final List ints = new ArrayList(classes.size());
        Set unspecified = null;
        for (Iterator iter = classes.iterator(); iter.hasNext(); ) {
            final Class cls = (Class) iter.next();
            final PCEnhancer enhancer = new PCEnhancer(conf, cls); 

            enhancer.setBytecodeWriter(new BytecodeWriter() {
                public void write(BCClass bc) throws IOException {
                    ManagedClassSubclasser.write(bc, enhancer, map,
                        cls, subs, ints);
                }
            });
            if (redefine)
                enhancer.setRedefine(true);
            enhancer.setCreateSubclass(true);
            enhancer.setAddDefaultConstructor(true);

            // set this before enhancement as well as after since enhancement
            // uses a different metadata repository, and the metadata config
            // matters in the enhancement contract. Don't do any warning here,
            // since we'll issue warnings when we do the final metadata
            // reconfiguration at the end of this method.
            configureMetaData(enhancer.getMetaData(), conf, redefine, false);

            unspecified = collectRelatedUnspecifiedTypes(enhancer.getMetaData(),
                classes, unspecified);

            int runResult = enhancer.run();
            if (runResult == PCEnhancer.ENHANCE_PC) {
                try {
                    enhancer.record();
                } catch (IOException e) {
                    // our impl of BytecodeWriter doesn't throw IOException
                    throw new InternalException(e);
                }
            }
        }

        if (unspecified != null && !unspecified.isEmpty())
            throw new UserException(_loc.get("unspecified-unenhanced-types",
                classes, unspecified));

        ClassRedefiner.redefineClasses(conf, map);
        for (Class cls : map.keySet()) {
            setIntercepting(conf, envLoader, cls);
            configureMetaData(conf, envLoader, cls, redefine);
        }
        for (Class cls : (Collection) subs)
            configureMetaData(conf, envLoader, cls, redefine);
        for (Class cls : (Collection) ints)
            setIntercepting(conf, envLoader, cls);

        return subs;
    }

    private static Set collectRelatedUnspecifiedTypes(ClassMetaData meta,
        Collection classes, Set unspecified) {
        unspecified = collectUnspecifiedType(meta.getPCSuperclass(), classes,
            unspecified);

        for (FieldMetaData fmd : meta.getFields()) {
            if (fmd.isTransient())
                continue;
            if (fmd.isTypePC())
                unspecified = collectUnspecifiedType(fmd.getType(), classes,
                    unspecified);
            if (fmd.getElement() != null && fmd.getElement().isTypePC())
                unspecified = collectUnspecifiedType(fmd.getElement().getType(),
                    classes, unspecified);
            if (fmd.getKey() != null && fmd.getKey().isTypePC())
                unspecified = collectUnspecifiedType(fmd.getKey().getType(),
                    classes, unspecified);
            if (fmd.getValue() != null && fmd.getValue().isTypePC())
                unspecified = collectUnspecifiedType(fmd.getValue().getType(),
                    classes, unspecified);
        }
        return unspecified;
    }

    private static Set collectUnspecifiedType(Class cls,
        Collection classes, Set unspecified) {
        if (cls != null && !classes.contains(cls)
            && !ImplHelper.isManagedType(null, cls)) {
            if (unspecified == null)
                unspecified = new HashSet();
            unspecified.add(cls);
        }
        return unspecified;
    }

    private static void configureMetaData(OpenJPAConfiguration conf,
        ClassLoader envLoader, Class cls, boolean redefineAvailable) {
        ClassMetaData meta = conf.getMetaDataRepositoryInstance()
            .getMetaData(cls, envLoader, true);
        configureMetaData(meta, conf, redefineAvailable, true);
    }

    private static void configureMetaData(ClassMetaData meta,
        OpenJPAConfiguration conf, boolean redefineAvailable, boolean warn) {

        setDetachedState(meta);

        if (warn && meta.getAccessType() == ClassMetaData.ACCESS_FIELD
            && !redefineAvailable) {
            // only warn about declared fields; superclass fields will be
            // warned about when the superclass is handled
            for (FieldMetaData fmd : meta.getDeclaredFields()) {
                switch (fmd.getTypeCode()) {
                    case JavaTypes.COLLECTION:
                    case JavaTypes.MAP:
                        // we can lazily load these, since we own the
                        // relationship container
                        break;
                    default:
                        if (!fmd.isInDefaultFetchGroup()
                            && !(fmd.isVersion() || fmd.isPrimaryKey())) {
                            Log log = conf.getLog(
                                OpenJPAConfiguration.LOG_ENHANCE);
                            log.warn(_loc.get("subclasser-fetch-group-override",
                                meta.getDescribedType().getName(),
                                fmd.getName()));
                            fmd.setInDefaultFetchGroup(true);
                        }
                }
            }
        }
    }

    private static void write(BCClass bc, PCEnhancer enhancer,
        Map map, Class cls, List subs, List ints)
        throws IOException {

        if (bc == enhancer.getManagedTypeBytecode()) {
            // if it was already defined, don't put it in the map,
            // but do set the metadata accordingly.
            if (enhancer.isAlreadyRedefined())
                ints.add(bc.getType());
            else if (JavaVersions.VERSION >= 5) {
                map.put(bc.getType(), bc.toByteArray());
                debugBytecodes(bc);
            }
        } else {
            if (!enhancer.isAlreadySubclassed()) {
                debugBytecodes(bc);
                
                // this is the new subclass
                ClassLoader loader = GeneratedClasses.getMostDerivedLoader(
                    cls, PersistenceCapable.class);
                subs.add(GeneratedClasses.loadBCClass(bc, loader));
            }
        }
    }

    private static void debugBytecodes(BCClass bc) throws IOException {
        // Write the bytecodes to disk for debugging purposes.
        if ("true".equals(System.getProperty(
            ManagedClassSubclasser.class.getName() + ".dumpBytecodes")))
        {
            File tmp = new File(System.getProperty("java.io.tmpdir"));
            File dir = new File(tmp, "openjpa");
            dir = new File(dir, "pcsubclasses");
            dir.mkdirs();
            dir = Files.getPackageFile(dir, bc.getPackageName(), true);
            File f = new File(dir, bc.getClassName() + ".class");
            System.err.println("Writing to " + f);
            bc.write(f);
        }
    }

    private static void setIntercepting(OpenJPAConfiguration conf,
        ClassLoader envLoader, Class cls) {
        ClassMetaData meta = conf.getMetaDataRepositoryInstance()
            .getMetaData(cls, envLoader, true);
        meta.setIntercepting(true);
    }

    /**
     * If the metadata is configured to use a synthetic
     * detached state, reset it to not use a detached
     * state field, since we can't add fields when redefining.
     */
    private static void setDetachedState(ClassMetaData meta) {
        if (ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()))
            meta.setDetachedState(null);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy