blob: dbbc4bb7fa6355e9bff9c6b698238335bafdc831 [file] [log] [blame]
/*
* 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.persistence;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import javax.persistence.Basic;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.lib.util.J2DoPriv5Helper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.meta.AbstractMetaDataDefaults;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
import static org.apache.openjpa.persistence.PersistenceStrategy.*;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.conf.OpenJPAConfiguration;
/**
* JPA-based metadata defaults.
*
* @author Patrick Linskey
* @author Abe White
* @nojavadoc
*/
public class PersistenceMetaDataDefaults
extends AbstractMetaDataDefaults {
private boolean _allowsMultipleMethodsForSameCallback = false;
private static final Localizer _loc = Localizer.forPackage
(PersistenceMetaDataDefaults.class);
private static final Map<Class, PersistenceStrategy> _strats =
new HashMap<Class, PersistenceStrategy>();
private static final Set<String> _ignoredAnnos = new HashSet<String>();
static {
_strats.put(Basic.class, BASIC);
_strats.put(ManyToOne.class, MANY_ONE);
_strats.put(OneToOne.class, ONE_ONE);
_strats.put(Embedded.class, EMBEDDED);
_strats.put(EmbeddedId.class, EMBEDDED);
_strats.put(OneToMany.class, ONE_MANY);
_strats.put(ManyToMany.class, MANY_MANY);
_strats.put(Persistent.class, PERS);
_strats.put(PersistentCollection.class, PERS_COLL);
_strats.put(PersistentMap.class, PERS_MAP);
_ignoredAnnos.add(DetachedState.class.getName());
_ignoredAnnos.add(PostLoad.class.getName());
_ignoredAnnos.add(PostPersist.class.getName());
_ignoredAnnos.add(PostRemove.class.getName());
_ignoredAnnos.add(PostUpdate.class.getName());
_ignoredAnnos.add(PrePersist.class.getName());
_ignoredAnnos.add(PreRemove.class.getName());
_ignoredAnnos.add(PreUpdate.class.getName());
}
public PersistenceMetaDataDefaults() {
setCallbackMode(CALLBACK_RETHROW | CALLBACK_ROLLBACK |
CALLBACK_FAIL_FAST);
setDataStoreObjectIdFieldUnwrapped(true);
}
/**
* Return the code for the strategy of the given member. Return null if
* no strategy.
*/
public static PersistenceStrategy getPersistenceStrategy
(FieldMetaData fmd, Member member) {
if (member == null)
return null;
AnnotatedElement el = (AnnotatedElement) member;
if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
.isAnnotationPresentAction(el, Transient.class))).booleanValue())
return TRANSIENT;
if (fmd != null
&& fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
return null;
// look for persistence strategy in annotation table
PersistenceStrategy pstrat = null;
for (Annotation anno : el.getDeclaredAnnotations()) {
if (pstrat != null && _strats.containsKey(anno.annotationType()))
throw new MetaDataException(_loc.get("already-pers", member));
if (pstrat == null)
pstrat = _strats.get(anno.annotationType());
}
if (pstrat != null)
return pstrat;
Class type;
int code;
if (fmd != null) {
type = fmd.getType();
code = fmd.getTypeCode();
} else if (member instanceof Field) {
type = ((Field) member).getType();
code = JavaTypes.getTypeCode(type);
} else {
type = ((Method) member).getReturnType();
code = JavaTypes.getTypeCode(type);
}
switch (code) {
case JavaTypes.ARRAY:
if (type == byte[].class
|| type == char[].class
|| type == Byte[].class
|| type == Character[].class)
return BASIC;
break;
case JavaTypes.BOOLEAN:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT:
case JavaTypes.SHORT_OBJ:
case JavaTypes.STRING:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
case JavaTypes.DATE:
return BASIC;
case JavaTypes.OBJECT:
if (Enum.class.isAssignableFrom(type))
return BASIC;
break;
}
//### EJB3: what if defined in XML?
if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
.isAnnotationPresentAction(type, Embeddable.class))).booleanValue())
return EMBEDDED;
if (Serializable.class.isAssignableFrom(type))
return BASIC;
return null;
}
/**
* Flags if multiple methods of the same class can handle the same
* callback event.
*/
public boolean getAllowsMultipleMethodsForSameCallback() {
return _allowsMultipleMethodsForSameCallback;
}
/**
* Flags if multiple methods of the same class can handle the same
* callback event.
*/
public void setAllowsMultipleMethodsForSameCallback(boolean flag) {
_allowsMultipleMethodsForSameCallback = flag;
}
/**
* Auto-configuration method for the default access type of base classes
* with ACCESS_UNKNOWN
*/
public void setDefaultAccessType(String type) {
if (type == null)
return;
if ("PROPERTY".equals(type.toUpperCase()))
setDefaultAccessType(ClassMetaData.ACCESS_PROPERTY);
else
setDefaultAccessType(ClassMetaData.ACCESS_FIELD);
}
@Override
public void populate(ClassMetaData meta, int access) {
super.populate(meta, access);
meta.setDetachable(true);
// do not call get*Fields as it will lock down the fields.
}
@Override
protected void populate(FieldMetaData fmd) {
setCascadeNone(fmd);
setCascadeNone(fmd.getKey());
setCascadeNone(fmd.getElement());
}
/**
* Turns off auto cascading of persist, refresh, attach.
*/
static void setCascadeNone(ValueMetaData vmd) {
vmd.setCascadePersist(ValueMetaData.CASCADE_NONE);
vmd.setCascadeRefresh(ValueMetaData.CASCADE_NONE);
vmd.setCascadeAttach(ValueMetaData.CASCADE_NONE);
}
@Override
protected int getAccessType(ClassMetaData meta) {
return getAccessType(meta.getDescribedType());
}
/**
* Recursive helper to determine access type based on annotation placement.
*/
private int getAccessType(Class cls) {
// traversed entire hierarchy without finding annotations
if (cls == null || cls == Object.class)
return ClassMetaData.ACCESS_UNKNOWN;
int access = 0;
if (annotated((Field[]) AccessController.doPrivileged(
J2DoPriv5Helper.getDeclaredFieldsAction(cls))).size() > 0)
access |= ClassMetaData.ACCESS_FIELD;
if (annotated((Method[]) AccessController.doPrivileged(
J2DoPriv5Helper.getDeclaredMethodsAction(cls))).size() > 0
|| cls.isInterface()) // OpenJPA managed ifaces must use prop access
access |= ClassMetaData.ACCESS_PROPERTY;
return getAccessType(cls.getSuperclass()) | access;
}
@Override
protected List getFieldAccessNames(ClassMetaData meta) {
return annotated((Field[]) AccessController.doPrivileged(
J2DoPriv5Helper.getDeclaredFieldsAction(meta.getDescribedType())));
}
@Override
protected List getPropertyAccessNames(ClassMetaData meta) {
return annotated((Method[]) AccessController.doPrivileged(
J2DoPriv5Helper.getDeclaredMethodsAction(meta.getDescribedType())));
}
/**
* Return the members of <code>members</code> that have persistence
* annotations.
*/
private static List annotated(AnnotatedElement[] members) {
Annotation[] annos;
String name;
List annotated = new ArrayList(members.length);
for (int i = 0; i < members.length; i++) {
annos = (Annotation[]) AccessController.doPrivileged(J2DoPriv5Helper
.getAnnotationsAction(members[i]));
for (int j = 0; j < annos.length; j++) {
name = annos[j].annotationType().getName();
if ((name.startsWith("javax.persistence.")
|| name.startsWith("org.apache.openjpa.persistence."))
&& !_ignoredAnnos.contains(name))
annotated.add(members[i]);
}
}
return annotated;
}
protected boolean isDefaultPersistent(ClassMetaData meta, Member member,
String name) {
int mods = member.getModifiers();
if (Modifier.isTransient(mods))
return false;
if (member instanceof Method) {
try {
// check for setters for methods
Method setter = (Method) AccessController.doPrivileged(
J2DoPriv5Helper.getDeclaredMethodAction(
meta.getDescribedType(), "set" +
StringUtils.capitalize(name), new Class[] {
((Method) member).getReturnType() }));
if (setter == null && !isAnnotatedTransient(member)) {
logNoSetter(meta, name, null);
return false;
}
} catch (Exception e) {
// e.g., NoSuchMethodException
if (!isAnnotatedTransient(member))
logNoSetter(meta, name, e);
return false;
}
}
PersistenceStrategy strat = getPersistenceStrategy(null, member);
if (strat == null || strat == PersistenceStrategy.TRANSIENT)
return false;
return true;
}
private boolean isAnnotatedTransient(Member member) {
return member instanceof AnnotatedElement
&& ((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
.isAnnotationPresentAction(((AnnotatedElement) member),
Transient.class))).booleanValue();
}
private void logNoSetter(ClassMetaData meta, String name, Exception e) {
Log log = meta.getRepository().getConfiguration()
.getLog(OpenJPAConfiguration.LOG_METADATA);
if (log.isWarnEnabled())
log.warn(_loc.get("no-setter-for-getter", name,
meta.getDescribedType().getName()));
else if (log.isTraceEnabled())
// log the exception, if any, if we're in trace-level debugging
log.warn(_loc.get("no-setter-for-getter", name,
meta.getDescribedType().getName()), e);
}
}