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

org.apache.openjpa.enhance.PCSubclassValidator 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.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.util.UserException;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.lib.log.Log;
import serp.bytecode.BCField;
import serp.bytecode.BCClass;
import serp.bytecode.BCMethod;

/**
 *	

Validates that a given type meets the JPA contract, plus a few * OpenJPA-specific additions for subclassing / redefinition: * *

    *
  • must have an accessible no-args constructor
  • *
  • must be a public or protected class
  • *
  • must not be final
  • *
  • must not extend an enhanced class
  • *
  • all persistent data represented by accessible setter/getter * methods (persistent properties)
  • *
  • if versioning is to be used, exactly one persistent property for * the numeric version data
  • * *
  • When using property access, the backing field for a persistent * property must be: *
      * *
    • private
    • *
    • set only in the designated setter, * in the constructor, or in {@link Object#clone()}, * readObject(ObjectInputStream), or * {@link Externalizable#readExternal(ObjectInput)}.
    • *
    • read only in the designated getter and the * constructor.
    • *
    *
  • *
* *

If you use this technique and use the new keyword instead * of a OpenJPA-supplied construction routine, OpenJPA will need to do extra * work with persistent-new-flushed instances, since OpenJPA cannot in this * case track what happens to such an instance.

* * @since 1.0.0 */ public class PCSubclassValidator { private static final Localizer loc = Localizer.forPackage(PCSubclassValidator.class); private final ClassMetaData meta; private final BCClass pc; private final Log log; private final boolean failOnContractViolations; private Collection errors; private Collection contractViolations; public PCSubclassValidator(ClassMetaData meta, BCClass bc, Log log, boolean enforceContractViolations) { this.meta = meta; this.pc = bc; this.log = log; this.failOnContractViolations = enforceContractViolations; } public void assertCanSubclass() { Class superclass = meta.getDescribedType(); String name = superclass.getName(); if (superclass.isInterface()) addError(loc.get("subclasser-no-ifaces", name), meta); if (Modifier.isFinal(superclass.getModifiers())) addError(loc.get("subclasser-no-final-classes", name), meta); if (Modifier.isPrivate(superclass.getModifiers())) addError(loc.get("subclasser-no-private-classes", name), meta); if (PersistenceCapable.class.isAssignableFrom(superclass)) addError(loc.get("subclasser-super-already-pc", name), meta); try { Constructor c = superclass.getDeclaredConstructor(new Class[0]); if (!(Modifier.isProtected(c.getModifiers()) || Modifier.isPublic(c.getModifiers()))) addError(loc.get("subclasser-private-ctor", name), meta); } catch (NoSuchMethodException e) { addError(loc.get("subclasser-no-void-ctor", name), meta); } // if the BCClass we loaded is already pc and the superclass is not, // then we should never get here, so let's make sure that the // calling context is caching correctly by throwing an exception. if (pc.isInstanceOf(PersistenceCapable.class) && !PersistenceCapable.class.isAssignableFrom(superclass)) throw new InternalException( loc.get("subclasser-class-already-pc", name)); if (AccessCode.isProperty(meta.getAccessType())) checkPropertiesAreInterceptable(); if (errors != null && !errors.isEmpty()) throw new UserException(errors.toString()); else if (contractViolations != null && !contractViolations.isEmpty() && log.isWarnEnabled()) log.warn(contractViolations.toString()); } private void checkPropertiesAreInterceptable() { // just considers accessor methods for now. FieldMetaData[] fmds = meta.getFields(); for (int i = 0; i < fmds.length; i++) { Method getter = getBackingMember(fmds[i]); if (getter == null) { addError(loc.get("subclasser-no-getter", fmds[i].getName()), fmds[i]); continue; } BCField returnedField = checkGetterIsSubclassable(getter, fmds[i]); Method setter = setterForField(fmds[i]); if (setter == null) { addError(loc.get("subclasser-no-setter", fmds[i].getName()), fmds[i]); continue; } BCField assignedField = checkSetterIsSubclassable(setter, fmds[i]); if (assignedField == null) continue; if (assignedField != returnedField) addContractViolation(loc.get ("subclasser-setter-getter-field-mismatch", fmds[i].getName(), returnedField,assignedField), fmds[i]); // ### scan through all the rest of the class to make sure it // ### doesn't use the field. } } private Method getBackingMember(FieldMetaData fmd) { Member back = fmd.getBackingMember(); if (Method.class.isInstance(back)) return (Method)back; Method getter = Reflection.findGetter(meta.getDescribedType(), fmd.getName(), false); if (getter != null) fmd.backingMember(getter); return getter; } private Method setterForField(FieldMetaData fmd) { try { return fmd.getDeclaringType().getDeclaredMethod( "set" + StringUtils.capitalize(fmd.getName()), new Class[]{ fmd.getDeclaredType() }); } catch (NoSuchMethodException e) { return null; } } /** * @return the name of the field that is returned by meth, or * null if something other than a single field is * returned, or if it cannot be determined what is returned. */ private BCField checkGetterIsSubclassable(Method meth, FieldMetaData fmd) { checkMethodIsSubclassable(meth, fmd); BCField field = PCEnhancer.getReturnedField(getBCMethod(meth)); if (field == null) { addContractViolation(loc.get("subclasser-invalid-getter", fmd.getName()), fmd); return null; } else { return field; } } /** * @return the field that is set in meth, or * null if something other than a single field is * set, or if it cannot be determined what is set. */ private BCField checkSetterIsSubclassable(Method meth, FieldMetaData fmd) { checkMethodIsSubclassable(meth, fmd); BCField field = PCEnhancer.getAssignedField(getBCMethod(meth)); if (field == null) { addContractViolation(loc.get("subclasser-invalid-setter", fmd.getName()), fmd); return null; } else { return field; } } private BCMethod getBCMethod(Method meth) { BCClass bc = pc.getProject().loadClass(meth.getDeclaringClass()); return bc.getDeclaredMethod(meth.getName(), meth.getParameterTypes()); } private void checkMethodIsSubclassable(Method meth, FieldMetaData fmd) { String className = fmd.getDefiningMetaData(). getDescribedType().getName(); if (!(Modifier.isProtected(meth.getModifiers()) || Modifier.isPublic(meth.getModifiers()))) addError(loc.get("subclasser-private-accessors-unsupported", className, meth.getName()), fmd); if (Modifier.isFinal(meth.getModifiers())) addError(loc.get("subclasser-final-methods-not-allowed", className, meth.getName()), fmd); if (Modifier.isNative(meth.getModifiers())) addContractViolation(loc.get ("subclasser-native-methods-not-allowed", className, meth.getName()), fmd); if (Modifier.isStatic(meth.getModifiers())) addError(loc.get("subclasser-static-methods-not-supported", className, meth.getName()), fmd); } private void addError(Message s, ClassMetaData cls) { if (errors == null) errors = new ArrayList(); errors.add(loc.get("subclasser-error-meta", s, cls.getDescribedType().getName(), cls.getSourceFile())); } private void addError(Message s, FieldMetaData fmd) { if (errors == null) errors = new ArrayList(); errors.add(loc.get("subclasser-error-field", s, fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile())); } private void addContractViolation(Message m, FieldMetaData fmd) { // add the violation as an error in case we're processing violations // as errors; this keeps them in the order that they were found rather // than just adding the violations to the end of the list. if (failOnContractViolations) addError(m, fmd); if (contractViolations == null) contractViolations = new ArrayList(); contractViolations.add(loc.get ("subclasser-contract-violation-field", m.getMessage(), fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile())); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy