| /* |
| * 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.meta; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.security.PrivilegedActionException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.openjpa.enhance.PCRegistry; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.util.OpenJPAException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Abstract implementation provides a set of generic utilities for detecting |
| * persistence meta-data of Field/Member. Also provides bean-style properties |
| * such as access style or identity type to be used by default when such |
| * information is not derivable from available meta-data. |
| * |
| * @author Abe White |
| * @author Pinaki Poddar |
| */ |
| public abstract class AbstractMetaDataDefaults |
| implements MetaDataDefaults { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (AbstractMetaDataDefaults.class); |
| |
| private int _access = AccessCode.FIELD; |
| private int _identity = ClassMetaData.ID_UNKNOWN; |
| private boolean _ignore = true; |
| private boolean _interface = true; |
| private boolean _pcRegistry = true; |
| private int _callback = CALLBACK_RETHROW; |
| private boolean _unwrapped = false; |
| |
| /** |
| * Whether to attempt to use the information from registered classes |
| * to populate metadata defaults. Defaults to true. |
| */ |
| public boolean getUsePCRegistry() { |
| return _pcRegistry; |
| } |
| |
| /** |
| * Whether to attempt to use the information from registered classes |
| * to populate metadata defaults. Defaults to true. |
| */ |
| public void setUsePCRegistry(boolean pcRegistry) { |
| _pcRegistry = pcRegistry; |
| } |
| |
| /** |
| * The default access type for base classes with ACCESS_UNKNOWN. |
| * ACCESS_FIELD by default. |
| */ |
| @Override |
| public int getDefaultAccessType() { |
| return _access; |
| } |
| |
| /** |
| * The default access type for base classes with ACCESS_UNKNOWN. |
| * ACCESS_FIELD by default. |
| */ |
| public void setDefaultAccessType(int access) { |
| _access = access; |
| } |
| |
| /** |
| * The default identity type for unmapped classes without primary |
| * key fields. ID_UNKNOWN by default. |
| */ |
| @Override |
| public int getDefaultIdentityType() { |
| return _identity; |
| } |
| |
| /** |
| * The default identity type for unmapped classes without primary |
| * key fields. ID_UNKNOWN by default. |
| */ |
| public void setDefaultIdentityType(int identity) { |
| _identity = identity; |
| } |
| |
| @Override |
| public int getCallbackMode() { |
| return _callback; |
| } |
| |
| public void setCallbackMode(int mode) { |
| _callback = mode; |
| } |
| |
| public void setCallbackMode(int mode, boolean on) { |
| if (on) |
| _callback |= mode; |
| else |
| _callback &= ~mode; |
| } |
| |
| @Override |
| public boolean getCallbacksBeforeListeners(int type) { |
| return false; |
| } |
| |
| @Override |
| public boolean isDeclaredInterfacePersistent() { |
| return _interface; |
| } |
| |
| public void setDeclaredInterfacePersistent(boolean pers) { |
| _interface = pers; |
| } |
| |
| @Override |
| public boolean isDataStoreObjectIdFieldUnwrapped() { |
| return _unwrapped; |
| } |
| |
| public void setDataStoreObjectIdFieldUnwrapped(boolean unwrapped) { |
| _unwrapped = unwrapped; |
| } |
| |
| public boolean getIgnoreNonPersistent() { |
| return _ignore; |
| } |
| |
| @Override |
| public void setIgnoreNonPersistent(boolean ignore) { |
| _ignore = ignore; |
| } |
| |
| @Override |
| public void populate(ClassMetaData meta, int access) { |
| populate(meta, access, false); |
| } |
| |
| @Override |
| public void populate(ClassMetaData meta, int access, boolean ignoreTransient) { |
| if (meta.getDescribedType() == Object.class) |
| return; |
| meta.setAccessType(access); |
| |
| Log log = meta.getRepository().getLog(); |
| if (log.isTraceEnabled()) |
| log.trace(_loc.get("gen-meta", meta)); |
| if (!_pcRegistry || !populateFromPCRegistry(meta)) { |
| if (log.isTraceEnabled()) |
| log.trace(_loc.get("meta-reflect")); |
| populateFromReflection(meta, ignoreTransient); |
| } |
| } |
| |
| /** |
| * Populate the given metadata using the {@link PCRegistry}. |
| */ |
| private boolean populateFromPCRegistry(ClassMetaData meta) { |
| Class<?> cls = meta.getDescribedType(); |
| if (!PCRegistry.isRegistered(cls)) |
| return false; |
| try { |
| String[] fieldNames = PCRegistry.getFieldNames(cls); |
| Class<?>[] fieldTypes = PCRegistry.getFieldTypes(cls); |
| Member member; |
| FieldMetaData fmd; |
| for (int i = 0; i < fieldNames.length; i ++) { |
| String property = fieldNames[i]; |
| member = getMemberByProperty(meta, property, |
| AccessCode.UNKNOWN, true); |
| if (member == null) // transient or indeterminable access |
| continue; |
| fmd = meta.addDeclaredField(property, fieldTypes[i]); |
| fmd.backingMember(member); |
| populate(fmd); |
| } |
| return true; |
| } catch (OpenJPAException ke) { |
| throw ke; |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) |
| e = ((PrivilegedActionException) e).getException(); |
| throw new UserException(e); |
| } |
| } |
| |
| protected abstract List<Member> getPersistentMembers(ClassMetaData meta, boolean ignoreTransient); |
| /** |
| * Generate the given meta-data using reflection. |
| * Adds FieldMetaData for each persistent state. |
| * Delegate to concrete implementation to determine the persistent |
| * members. |
| */ |
| private void populateFromReflection(ClassMetaData meta, boolean ignoreTransient) { |
| List<Member> members = getPersistentMembers(meta, ignoreTransient); |
| boolean iface = meta.getDescribedType().isInterface(); |
| // If access is mixed or if the default is currently unknown, |
| // process all fields, otherwise only process members of the class |
| // level default access type. |
| |
| String name; |
| boolean def; |
| FieldMetaData fmd; |
| for (Member member : members) { |
| name = getFieldName(member); |
| if (name == null || isReservedFieldName(name)) |
| continue; |
| |
| def = isDefaultPersistent(meta, member, name, ignoreTransient); |
| if (!def && _ignore) |
| continue; |
| |
| // passed the tests; persistent type -- we construct with |
| // Object.class because setting backing member will set proper |
| // type anyway |
| fmd = meta.addDeclaredField(name, Object.class); |
| fmd.backingMember(member); |
| if (!def) { |
| fmd.setExplicit(true); |
| fmd.setManagement(FieldMetaData.MANAGE_NONE); |
| } |
| populate(fmd); |
| } |
| } |
| |
| protected void populate(FieldMetaData fmd) { |
| |
| } |
| |
| /** |
| * Return the list of fields in <code>meta</code> that use field access, |
| * or <code>null</code> if a list of fields is unobtainable. An empty list |
| * should be returned if the list of fields is obtainable, but there |
| * happens to be no field access in <code>meta</code>. |
| * |
| * This is used for error reporting purposes only, so need not be efficient. |
| * |
| * This implementation returns <code>null</code>. |
| */ |
| protected List<String> getFieldAccessNames(ClassMetaData meta) { |
| return null; |
| } |
| |
| /** |
| * Return the list of methods in <code>meta</code> that use property access, |
| * or <code>null</code> if a list of methods is unobtainable. An empty list |
| * should be returned if the list of methods is obtainable, but there |
| * happens to be no property access in <code>meta</code>. |
| * |
| * This is used for error reporting purposes only, so need not be efficient. |
| * |
| * This implementation returns <code>null</code>. |
| */ |
| protected List<String> getPropertyAccessNames(ClassMetaData meta) { |
| return null; |
| } |
| |
| /** |
| * Return the field name for the given member. This will only be invoked |
| * on members of the right type (field vs. method). Return null if the |
| * member cannot be managed. Default behavior: For fields, returns the |
| * field name. For getter methods, returns the minus "get" or "is" with |
| * the next letter lower-cased. For other methods, returns null. |
| */ |
| public static String getFieldName(Member member) { |
| if (member instanceof Field) |
| return member.getName(); |
| if (!(member instanceof Method)) |
| return null; |
| Method method = (Method) member; |
| String name = method.getName(); |
| if (isNormalGetter(method)) |
| name = name.substring("get".length()); |
| else if (isBooleanGetter(method)) |
| name = name.substring("is".length()); |
| else |
| return null; |
| |
| if (name.length() == 1) |
| return name.toLowerCase(); |
| return Character.toLowerCase(name.charAt(0)) + name.substring(1); |
| } |
| |
| /** |
| * Returns true if the given field name is reserved for unmanaged fields. |
| */ |
| protected boolean isReservedFieldName(String name) { |
| // names used by enhancers |
| return name.startsWith("openjpa") || name.startsWith("jdo"); |
| } |
| |
| /** |
| * Return true if the given member is persistent by default. This will |
| * only be invoked on members of the right type (field vs. method). |
| * Returns false if member is static or final by default. |
| * |
| * @param name the field name from {@link #getFieldName} |
| */ |
| protected abstract boolean isDefaultPersistent(ClassMetaData meta, |
| Member member, String name, boolean ignoreTransient); |
| |
| /** |
| * Gets the backing member of the given field. If the field has not been |
| * assigned a backing member then get either the instance field or the |
| * getter method depending upon the access style of the defining class. |
| * <br> |
| * Defining class is used instead of declaring class because this method |
| * may be invoked during parsing phase when declaring metadata may not be |
| * available. |
| */ |
| @Override |
| public Member getBackingMember(FieldMetaData fmd) { |
| if (fmd == null) |
| return null; |
| if (fmd.getBackingMember() != null) |
| return fmd.getBackingMember(); |
| return getMemberByProperty(fmd.getDeclaringMetaData(), fmd.getName(), |
| fmd.getAccessType(), true); |
| } |
| |
| @Override |
| public Class<?> getUnimplementedExceptionType() { |
| return UnsupportedOperationException.class; |
| } |
| |
| /** |
| * Helper method; returns true if the given class appears to be |
| * user-defined. |
| */ |
| protected static boolean isUserDefined(Class<?> cls) { |
| return cls != null && !cls.getName().startsWith("java.") |
| && !cls.getName().startsWith ("javax."); |
| } |
| |
| /** |
| * Affirms if the given method matches the following signature |
| * <code> public T getXXX() </code> |
| * where T is any non-void type. |
| */ |
| public static boolean isNormalGetter(Method method) { |
| String methodName = method.getName(); |
| return startsWith(methodName, "get") |
| && method.getParameterTypes().length == 0 |
| && method.getReturnType() != void.class; |
| } |
| |
| /** |
| * Affirms if the given method matches the following signature |
| * <code> public boolean isXXX() </code> |
| * <code> public Boolean isXXX() </code> |
| */ |
| public static boolean isBooleanGetter(Method method) { |
| String methodName = method.getName(); |
| return startsWith(methodName, "is") |
| && method.getParameterTypes().length == 0 |
| && isBoolean(method.getReturnType()); |
| } |
| |
| /** |
| * Affirms if the given method signature matches bean-style getter method |
| * signature.<br> |
| * <code> public T getXXX()</code> where T is any non-void type.<br> |
| * or<br> |
| * <code> public T isXXX()</code> where T is boolean or Boolean.<br> |
| */ |
| public static boolean isGetter(Method method, boolean includePrivate) { |
| if (method == null) |
| return false; |
| int mods = method.getModifiers(); |
| if (!(Modifier.isPublic(mods) |
| || Modifier.isProtected(mods) |
| || (Modifier.isPrivate(mods) && includePrivate)) |
| || Modifier.isNative(mods) |
| || Modifier.isStatic(mods)) |
| return false; |
| return isNormalGetter(method) || isBooleanGetter(method); |
| } |
| |
| /** |
| * Affirms if the given full string starts with the given head. |
| */ |
| public static boolean startsWith(String full, String head) { |
| return full != null && head != null && full.startsWith(head) |
| && full.length() > head.length(); |
| } |
| |
| public static boolean isBoolean(Class<?> cls) { |
| return cls == boolean.class || cls == Boolean.class; |
| } |
| |
| public static List<String> toNames(List<? extends Member> members) { |
| List<String> result = new ArrayList<>(); |
| for (Member m : members) |
| result.add(m.getName()); |
| return result; |
| } |
| |
| } |