| /* | |
| * 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 | |
| * | |
| * https://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. | |
| */ | |
| /* | |
| * JDOImplHelper.java | |
| * | |
| */ | |
| package javax.jdo.spi; | |
| import java.lang.reflect.Constructor; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.security.PrivilegedAction; | |
| import java.text.DateFormat; | |
| import java.text.ParsePosition; | |
| import java.text.SimpleDateFormat; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.Currency; | |
| import java.util.Date; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.WeakHashMap; | |
| import javax.jdo.Constants; | |
| import javax.jdo.JDOException; | |
| import javax.jdo.JDOFatalInternalException; | |
| import javax.jdo.JDOFatalUserException; | |
| import javax.jdo.JDOUserException; | |
| import javax.jdo.LegacyJava; | |
| import javax.jdo.LegacyJava.SecurityManager; | |
| import javax.xml.parsers.DocumentBuilderFactory; | |
| import org.xml.sax.ErrorHandler; | |
| /** | |
| * This class is a helper class for JDO implementations. It contains methods to register metadata | |
| * for persistence-capable classes and to perform common operations needed by implementations, not | |
| * by end users. | |
| * | |
| * <p><code>JDOImplHelper</code> allows construction of instances of persistence-capable classes | |
| * without using reflection. | |
| * | |
| * <p>Persistence-capable classes register themselves via a static method at class load time. There | |
| * is no security restriction on this access. JDO implementations get access to the functions | |
| * provided by this class only if they are authorized by the security manager. To avoid having every | |
| * call go through the security manager, only the call to get an instance is checked. Once an | |
| * implementation has an instance, any of the methods can be invoked without security checks. | |
| * | |
| * @version 2.1 | |
| */ | |
| public class JDOImplHelper extends java.lang.Object { | |
| /** | |
| * This synchronized <code>HashMap</code> contains a static mapping of <code>PersistenceCapable | |
| * </code> class to metadata for the class used for constructing new instances. New entries are | |
| * added by the static method in each <code>PersistenceCapable</code> class. Entries are never | |
| * removed. | |
| */ | |
| private static final Map<Class<?>, Meta> registeredClasses = | |
| Collections.synchronizedMap(new HashMap<>()); | |
| /** | |
| * This Set contains all classes that have registered for setStateManager permissions via | |
| * authorizeStateManagerClass. Only the key is used in order to maintain a weak set of classes. | |
| */ | |
| private static final Map<Class<?>, Class<?>> authorizedStateManagerClasses = new WeakHashMap<>(); | |
| /** This list contains the registered listeners for <code>RegisterClassEvent</code>s. */ | |
| private static final List<RegisterClassListener> listeners = new ArrayList<>(); | |
| /** The list of registered StateInterrogation instances */ | |
| private static List<StateInterrogation> stateInterrogations = new ArrayList<>(); | |
| /** The singleton <code>JDOImplHelper</code> instance. */ | |
| private static final JDOImplHelper jdoImplHelper = new JDOImplHelper(); | |
| /** The Internationalization message helper. */ | |
| private static final I18NHelper msg = I18NHelper.getInstance("javax.jdo.Bundle"); // NOI18N | |
| private static final String ERR_NULL_CLASS = "ERR_NullClass"; // NOI18N | |
| /** The DateFormat pattern. */ | |
| private static String dateFormatPattern; | |
| /** The default DateFormat instance. */ | |
| private static DateFormat dateFormat; | |
| /** The DocumentBuilderFactory used during jdoconfig.xml parsing. */ | |
| private static DocumentBuilderFactory documentBuilderFactory; | |
| /** The ErrorHandler used during jdoconfig.xml parsing. */ | |
| private static ErrorHandler errorHandler; | |
| /** JDO standard properties that the user can configure. */ | |
| public static final Set<String> USER_CONFIGURABLE_STANDARD_PROPERTIES = | |
| createUserConfigurableStandardProperties(); | |
| private static Set<String> createUserConfigurableStandardProperties() { | |
| Set<String> props = new HashSet<>(); | |
| props.add(Constants.PROPERTY_CONNECTION_DRIVER_NAME); | |
| props.add(Constants.PROPERTY_CONNECTION_FACTORY2_NAME); | |
| props.add(Constants.PROPERTY_CONNECTION_FACTORY_NAME); | |
| props.add(Constants.PROPERTY_CONNECTION_PASSWORD); | |
| props.add(Constants.PROPERTY_CONNECTION_URL); | |
| props.add(Constants.PROPERTY_CONNECTION_USER_NAME); | |
| props.add(Constants.PROPERTY_COPY_ON_ATTACH); | |
| props.add(Constants.PROPERTY_DATASTORE_READ_TIMEOUT_MILLIS); | |
| props.add(Constants.PROPERTY_DATASTORE_WRITE_TIMEOUT_MILLIS); | |
| props.add(Constants.PROPERTY_DETACH_ALL_ON_COMMIT); | |
| props.add(Constants.PROPERTY_IGNORE_CACHE); | |
| props.add(Constants.PROPERTY_INSTANCE_LIFECYCLE_LISTENER); | |
| props.add(Constants.PROPERTY_MAPPING); | |
| props.add(Constants.PROPERTY_MAPPING_CATALOG); | |
| props.add(Constants.PROPERTY_MAPPING_SCHEMA); | |
| props.add(Constants.PROPERTY_MULTITHREADED); | |
| props.add(Constants.PROPERTY_NAME); | |
| props.add(Constants.PROPERTY_NONTRANSACTIONAL_READ); | |
| props.add(Constants.PROPERTY_NONTRANSACTIONAL_WRITE); | |
| props.add(Constants.PROPERTY_OPTIMISTIC); | |
| props.add(Constants.PROPERTY_PERSISTENCE_MANAGER_FACTORY_CLASS); | |
| props.add(Constants.PROPERTY_PERSISTENCE_UNIT_NAME); | |
| props.add(Constants.PROPERTY_READONLY); | |
| props.add(Constants.PROPERTY_RESTORE_VALUES); | |
| props.add(Constants.PROPERTY_RETAIN_VALUES); | |
| props.add(Constants.PROPERTY_SERVER_TIME_ZONE_ID); | |
| props.add(Constants.PROPERTY_SPI_RESOURCE_NAME); | |
| props.add(Constants.PROPERTY_TRANSACTION_ISOLATION_LEVEL); | |
| props.add(Constants.PROPERTY_TRANSACTION_TYPE); | |
| return Collections.unmodifiableSet(props); | |
| } | |
| static final String JAVAX_JDO_PREFIX_LOWER_CASED = Constants.JAVAX_JDO_PREFIX.toLowerCase(); | |
| static final String PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER_LOWER_CASED = | |
| Constants.PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER.toLowerCase(); | |
| static final Set<String> USER_CONFIGURABLE_STANDARD_PROPERTIES_LOWER_CASED = | |
| createUserConfigurableStandardPropertiesLowerCased(); | |
| static Set<String> createUserConfigurableStandardPropertiesLowerCased() { | |
| Set<String> mixedCased = createUserConfigurableStandardProperties(); | |
| Set<String> lowerCased = new HashSet<>(mixedCased.size()); | |
| for (String propertyName : mixedCased) { | |
| lowerCased.add(propertyName.toLowerCase()); | |
| } | |
| return Collections.unmodifiableSet(lowerCased); | |
| } | |
| /** Register the default DateFormat instance. */ | |
| static { | |
| jdoImplHelper.registerDateFormat(getDateTimeInstance()); | |
| } | |
| /** Creates new JDOImplHelper */ | |
| private JDOImplHelper() {} | |
| /** | |
| * Get an instance of <code>JDOImplHelper</code>. This method checks that the caller is authorized | |
| * for <code>JDOPermission("getMetadata")</code>, and if not, throws <code>SecurityException | |
| * </code>. | |
| * | |
| * @return an instance of <code>JDOImplHelper</code>. | |
| * @throws SecurityException if the caller is not authorized for JDOPermission("getMetadata"). | |
| */ | |
| public static JDOImplHelper getInstance() throws SecurityException { | |
| SecurityManager sec = LegacyJava.getSecurityManager(); | |
| if (sec != null) { | |
| // throws exception if caller is not authorized | |
| sec.checkPermission(JDOPermission.GET_METADATA); | |
| } | |
| return jdoImplHelper; | |
| } | |
| /** | |
| * Get the field names for a <code>PersistenceCapable</code> class. The order of fields is the | |
| * natural ordering of the <code>String</code> class (without considering localization). | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @return the field names for the class. | |
| */ | |
| public String[] getFieldNames(Class<?> pcClass) { | |
| Meta meta = getMeta(pcClass); | |
| return meta.getFieldNames(); | |
| } | |
| /** | |
| * Get the field types for a <code>PersistenceCapable</code> class. The order of fields is the | |
| * same as for field names. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @return the field types for the class. | |
| */ | |
| public Class<?>[] getFieldTypes(Class<?> pcClass) { | |
| Meta meta = getMeta(pcClass); | |
| return meta.getFieldTypes(); | |
| } | |
| /** | |
| * Get the field flags for a <code>PersistenceCapable</code> class. The order of fields is the | |
| * same as for field names. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @return the field types for the class. | |
| */ | |
| public byte[] getFieldFlags(Class<?> pcClass) { | |
| Meta meta = getMeta(pcClass); | |
| return meta.getFieldFlags(); | |
| } | |
| /** | |
| * Get the persistence-capable superclass for a <code>PersistenceCapable</code> class. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @return The <code>PersistenceCapable</code> superclass for this class, or <code>null</code> if | |
| * there isn't one. | |
| */ | |
| public Class<?> getPersistenceCapableSuperclass(Class<?> pcClass) { | |
| Meta meta = getMeta(pcClass); | |
| return meta.getPersistenceCapableSuperclass(); | |
| } | |
| /** | |
| * Create a new instance of the class and assign its <code>jdoStateManager</code>. The new | |
| * instance has its <code>jdoFlags</code> set to <code>LOAD_REQUIRED</code>. | |
| * | |
| * @see PersistenceCapable#jdoNewInstance(StateManager sm) | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @param sm the <code>StateManager</code> which will own the new instance. | |
| * @return the new instance, or <code>null</code> if the class is not registered. | |
| */ | |
| public PersistenceCapable newInstance(Class<?> pcClass, StateManager sm) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| return pcInstance == null ? null : pcInstance.jdoNewInstance(sm); | |
| } | |
| /** | |
| * Create a new instance of the class and assign its <code>jdoStateManager</code> and key values | |
| * from the ObjectId. If the oid parameter is <code>null</code>, no key values are copied. The new | |
| * instance has its <code>jdoFlags</code> set to <code>LOAD_REQUIRED</code>. | |
| * | |
| * @see PersistenceCapable#jdoNewInstance(StateManager sm, Object oid) | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @param sm the <code>StateManager</code> which will own the new instance. | |
| * @return the new instance, or <code>null</code> if the class is not registered. | |
| * @param oid the ObjectId instance from which to copy key field values. | |
| */ | |
| public PersistenceCapable newInstance(Class<?> pcClass, StateManager sm, Object oid) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| return pcInstance == null ? null : pcInstance.jdoNewInstance(sm, oid); | |
| } | |
| /** | |
| * Create a new instance of the ObjectId class of this <code>PersistenceCapable</code> class. It | |
| * is intended only for application identity. This method should not be called for classes that | |
| * use single field identity; newObjectIdInstance(Class, Object) should be used instead. If the | |
| * class has been enhanced for datastore identity, or if the class is abstract, null is returned. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @return the new ObjectId instance, or <code>null</code> if the class is not registered. | |
| */ | |
| public Object newObjectIdInstance(Class<?> pcClass) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| return pcInstance == null ? null : pcInstance.jdoNewObjectIdInstance(); | |
| } | |
| /** | |
| * Create a new instance of the class used by the parameter Class for JDO identity, using the key | |
| * constructor of the object id class. It is intended for single field identity. The identity | |
| * instance returned has no relationship with the values of the primary key fields of the | |
| * persistence-capable instance on which the method is called. If the key is the wrong class for | |
| * the object id class, null is returned. | |
| * | |
| * <p>For classes that use single field identity, if the parameter is of one of the following | |
| * types, the behavior must be as specified: | |
| * | |
| * <ul> | |
| * <li><code>Number</code> or <code>Character</code>: the parameter must be the single field | |
| * type or the wrapper class of the primitive field type; the parameter is passed to the | |
| * single field identity constructor | |
| * <li><code>ObjectIdFieldSupplier</code>: the field value is fetched from the <code> | |
| * ObjectIdFieldSupplier</code> and passed to the single field identity constructor | |
| * <li><code>String</code>: the String is passed to the single field identity constructor | |
| * </ul> | |
| * | |
| * @return the new ObjectId instance, or <code>null</code> if the class is not registered. | |
| * @param obj the <code>Object</code> form of the object id | |
| * @param pcClass the <code>PersistenceCapable</code> class. | |
| * @since 2.0 | |
| */ | |
| public Object newObjectIdInstance(Class<?> pcClass, Object obj) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| return (pcInstance == null) ? null : pcInstance.jdoNewObjectIdInstance(obj); | |
| } | |
| /** | |
| * Copy fields from an outside source to the key fields in the ObjectId. This method is generated | |
| * in the <code>PersistenceCapable</code> class to generate a call to the field manager for each | |
| * key field in the ObjectId. | |
| * | |
| * <p>For example, an ObjectId class that has three key fields (<code>int id</code>, <code> | |
| * String name</code>, and <code>Float salary</code>) would have the method generated: | |
| * | |
| * <p><code> | |
| * void jdoCopyKeyFieldsToObjectId (Object oid, ObjectIdFieldSupplier fm) { | |
| * <BR> oid.id = fm.fetchIntField (0); | |
| * <BR> oid.name = fm.fetchStringField (1); | |
| * <BR> oid.salary = fm.fetchObjectField (2); | |
| * <BR>}</code> | |
| * | |
| * <p>The implementation is responsible for implementing the <code>ObjectIdFieldSupplier</code> to | |
| * provide the values for the key fields. | |
| * | |
| * @param pcClass the <code>PersistenceCapable Class</code>. | |
| * @param oid the ObjectId target of the copy. | |
| * @param fm the field manager that supplies the field values. | |
| */ | |
| public void copyKeyFieldsToObjectId( | |
| Class<?> pcClass, PersistenceCapable.ObjectIdFieldSupplier fm, Object oid) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| if (pcInstance == null) { | |
| throw new JDOFatalInternalException( | |
| msg.msg("ERR_AbstractClassNoIdentity", pcClass.getName())); // NOI18N | |
| } | |
| pcInstance.jdoCopyKeyFieldsToObjectId(fm, oid); | |
| } | |
| /** | |
| * Copy fields to an outside source from the key fields in the ObjectId. This method is generated | |
| * in the <code>PersistenceCapable</code> class to generate a call to the field manager for each | |
| * key field in the ObjectId. For example, an ObjectId class that has three key fields (<code> | |
| * int id</code>, <code>String name</code>, and <code>Float salary</code>) would have the method | |
| * generated: | |
| * | |
| * <p><code>void jdoCopyKeyFieldsFromObjectId | |
| * <BR> (PersistenceCapable oid, ObjectIdFieldConsumer fm) { | |
| * <BR> fm.storeIntField (0, oid.id); | |
| * <BR> fm.storeStringField (1, oid.name); | |
| * <BR> fm.storeObjectField (2, oid.salary); | |
| * <BR>}</code> | |
| * | |
| * <p>The implementation is responsible for implementing the <code>ObjectIdFieldConsumer</code> to | |
| * store the values for the key fields. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class | |
| * @param oid the ObjectId source of the copy. | |
| * @param fm the field manager that receives the field values. | |
| */ | |
| public void copyKeyFieldsFromObjectId( | |
| Class<?> pcClass, PersistenceCapable.ObjectIdFieldConsumer fm, Object oid) { | |
| Meta meta = getMeta(pcClass); | |
| PersistenceCapable pcInstance = meta.getPC(); | |
| if (pcInstance == null) { | |
| throw new JDOFatalInternalException( | |
| msg.msg("ERR_AbstractClassNoIdentity", pcClass.getName())); // NOI18N | |
| } | |
| pcInstance.jdoCopyKeyFieldsFromObjectId(fm, oid); | |
| } | |
| /** | |
| * Register metadata by class. The registration will be done in the class named <code> | |
| * JDOImplHelper</code> loaded by the same or an ancestor class loader as the <code> | |
| * PersistenceCapable</code> class performing the registration. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class used as the key for lookup. | |
| * @param fieldNames an array of <code>String</code> field names for persistent and transactional | |
| * fields | |
| * @param fieldTypes an array of <code>Class</code> field types | |
| * @param fieldFlags the Field Flags for persistent and transactional fields | |
| * @param pc an instance of the <code>PersistenceCapable</code> class | |
| * @param persistenceCapableSuperclass the most immediate superclass that is <code> | |
| * PersistenceCapable</code> | |
| */ | |
| public static void registerClass( | |
| Class<?> pcClass, | |
| String[] fieldNames, | |
| Class<?>[] fieldTypes, | |
| byte[] fieldFlags, | |
| Class<?> persistenceCapableSuperclass, | |
| PersistenceCapable pc) { | |
| if (pcClass == null) throw new NullPointerException(msg.msg(ERR_NULL_CLASS)); // NOI18N | |
| Meta meta = new Meta(fieldNames, fieldTypes, fieldFlags, persistenceCapableSuperclass, pc); | |
| registeredClasses.put(pcClass, meta); | |
| // handle class registration listeners | |
| synchronized (listeners) { | |
| if (!listeners.isEmpty()) { | |
| RegisterClassEvent event = | |
| new RegisterClassEvent( | |
| jdoImplHelper, | |
| pcClass, | |
| fieldNames, | |
| fieldTypes, | |
| fieldFlags, | |
| persistenceCapableSuperclass); | |
| for (Iterator<RegisterClassListener> i = listeners.iterator(); i.hasNext(); ) { | |
| RegisterClassListener crl = i.next(); | |
| if (crl != null) { | |
| crl.registerClass(event); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Unregister metadata by class loader. This method unregisters all registered <code> | |
| * PersistenceCapable</code> classes loaded by the specified class loader. Any attempt to get | |
| * metadata for unregistered classes will result in a <code>JDOFatalUserException</code>. | |
| * | |
| * @param cl the class loader. | |
| * @since 1.0.2 | |
| */ | |
| public void unregisterClasses(ClassLoader cl) { | |
| SecurityManager sec = LegacyJava.getSecurityManager(); | |
| if (sec != null) { | |
| // throws exception if caller is not authorized | |
| sec.checkPermission(JDOPermission.MANAGE_METADATA); | |
| } | |
| synchronized (registeredClasses) { | |
| for (Iterator<Class<?>> i = registeredClasses.keySet().iterator(); i.hasNext(); ) { | |
| Class<?> pcClass = i.next(); | |
| // Note, the pc class was registered by calling the static | |
| // method JDOImplHelper.registerClass. This means the | |
| // JDOImplHelper class loader is the same as or an ancestor | |
| // of the class loader of the pc class. In this case method | |
| // getClassLoader does not perform a security check for | |
| // RuntimePermission("getClassLoader") and thus we do not | |
| // need a privileged block for the getClassLoader call. | |
| if ((pcClass != null) && (pcClass.getClassLoader() == cl)) { | |
| // unregister pc class, if its class loader is the | |
| // specified one. | |
| i.remove(); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Unregister metadata by class. This method unregisters the specified class. Any further attempt | |
| * to get metadata for the specified class will result in a <code>JDOFatalUserException</code>. | |
| * | |
| * @param pcClass the <code>PersistenceCapable</code> class to be unregistered. | |
| * @since 1.0.2 | |
| */ | |
| public void unregisterClass(Class<?> pcClass) { | |
| if (pcClass == null) throw new NullPointerException(msg.msg(ERR_NULL_CLASS)); // NOI18N | |
| SecurityManager sec = LegacyJava.getSecurityManager(); | |
| if (sec != null) { | |
| // throws exception if caller is not authorized | |
| sec.checkPermission(JDOPermission.MANAGE_METADATA); | |
| } | |
| registeredClasses.remove(pcClass); | |
| } | |
| /** | |
| * Add the specified <code>RegisterClassListener</code> to the listener list. | |
| * | |
| * @param crl the listener to be added | |
| */ | |
| public void addRegisterClassListener(RegisterClassListener crl) { | |
| HashSet<Class<?>> alreadyRegisteredClasses = null; | |
| synchronized (listeners) { | |
| listeners.add(crl); | |
| // Make a copy of the existing set of registered classes. | |
| // Between these two lines of code, any number of new class | |
| // registrations might occur, and will then all wait until this | |
| // synchronized block completes. Some of the class registrations | |
| // might be delivered twice to the newly registered listener. | |
| alreadyRegisteredClasses = new HashSet<>(registeredClasses.keySet()); | |
| } | |
| // new registrations will call the new listener while the following | |
| // occurs notify the new listener about already-registered classes | |
| for (Iterator<Class<?>> it = alreadyRegisteredClasses.iterator(); it.hasNext(); ) { | |
| Class<?> pcClass = it.next(); | |
| Meta meta = getMeta(pcClass); | |
| RegisterClassEvent event = | |
| new RegisterClassEvent( | |
| this, | |
| pcClass, | |
| meta.getFieldNames(), | |
| meta.getFieldTypes(), | |
| meta.getFieldFlags(), | |
| meta.getPersistenceCapableSuperclass()); | |
| crl.registerClass(event); | |
| } | |
| } | |
| /** | |
| * Remove the specified <code>RegisterClassListener</code> from the listener list. | |
| * | |
| * @param crl the listener to be removed | |
| */ | |
| public void removeRegisterClassListener(RegisterClassListener crl) { | |
| synchronized (listeners) { | |
| listeners.remove(crl); | |
| } | |
| } | |
| /** | |
| * Returns a collection of class objects of the registered persistence-capable classes. | |
| * | |
| * @return registered persistence-capable classes | |
| */ | |
| public Collection<Class<?>> getRegisteredClasses() { | |
| return Collections.unmodifiableCollection(registeredClasses.keySet()); | |
| } | |
| /** | |
| * Look up the metadata for a <code>PersistenceCapable</code> class. | |
| * | |
| * @param pcClass the <code>Class</code>. | |
| * @return the <code>Meta</code> for the <code>Class</code>. | |
| */ | |
| private static Meta getMeta(Class<?> pcClass) { | |
| Meta ret = registeredClasses.get(pcClass); | |
| if (ret == null) { | |
| throw new JDOFatalUserException(msg.msg("ERR_NoMetadata", pcClass.getName())); // NOI18N | |
| } | |
| return ret; | |
| } | |
| /** | |
| * Register a class authorized to replaceStateManager. The caller of this method must be | |
| * authorized for JDOPermission("setStateManager"). During replaceStateManager, a | |
| * persistence-capable class will call the corresponding checkAuthorizedStateManager and the class | |
| * of the instance of the parameter must have been registered. | |
| * | |
| * @param smClass a Class that is authorized for JDOPermission("setStateManager"). | |
| * @throws SecurityException if the caller is not authorized for JDOPermission("setStateManager"). | |
| * @since 1.0.1 | |
| */ | |
| public static void registerAuthorizedStateManagerClass(Class<?> smClass) | |
| throws SecurityException { | |
| if (smClass == null) throw new NullPointerException(msg.msg(ERR_NULL_CLASS)); // NOI18N | |
| SecurityManager sm = LegacyJava.getSecurityManager(); | |
| if (sm != null) { | |
| sm.checkPermission(JDOPermission.SET_STATE_MANAGER); | |
| } | |
| synchronized (authorizedStateManagerClasses) { | |
| authorizedStateManagerClasses.put(smClass, null); | |
| } | |
| } | |
| /** | |
| * Register classes authorized to replaceStateManager. The caller of this method must be | |
| * authorized for JDOPermission("setStateManager"). During replaceStateManager, a | |
| * persistence-capable class will call the corresponding checkAuthorizedStateManager and the class | |
| * of the instance of the parameter must have been registered. | |
| * | |
| * @param smClasses a Collection of Classes that are authorized for | |
| * JDOPermission("setStateManager"). | |
| * @throws SecurityException if the caller is not authorized for JDOPermission("setStateManager"). | |
| * @since 1.0.1 | |
| */ | |
| public static void registerAuthorizedStateManagerClasses(Collection<?> smClasses) | |
| throws SecurityException { | |
| SecurityManager sm = LegacyJava.getSecurityManager(); | |
| if (sm != null) { | |
| sm.checkPermission(JDOPermission.SET_STATE_MANAGER); | |
| synchronized (authorizedStateManagerClasses) { | |
| for (Iterator<?> it = smClasses.iterator(); it.hasNext(); ) { | |
| Object smClass = it.next(); | |
| if (!(smClass instanceof Class)) { | |
| throw new ClassCastException( | |
| msg.msg( | |
| "ERR_StateManagerClassCast", // NOI18N | |
| smClass.getClass().getName())); | |
| } | |
| registerAuthorizedStateManagerClass((Class<?>) it.next()); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Register a DocumentBuilderFactory instance for use in parsing the resource(s) | |
| * META-INF/jdoconfig.xml. The default is governed by the semantics of | |
| * DocumentBuilderFactory.newInstance(). | |
| * | |
| * @param factory the DocumentBuilderFactory instance to use | |
| * @since 2.1 | |
| */ | |
| public synchronized void registerDocumentBuilderFactory(DocumentBuilderFactory factory) { | |
| documentBuilderFactory = factory; | |
| } | |
| /** | |
| * Return the registered instance of DocumentBuilderFactory. | |
| * | |
| * @return the DocumentBuilderFactory if registered; null otherwise | |
| * @since 2.1 | |
| */ | |
| public static DocumentBuilderFactory getRegisteredDocumentBuilderFactory() { | |
| return documentBuilderFactory; | |
| } | |
| /** | |
| * Register an ErrorHandler instance for use in parsing the resource(s) META-INF/jdoconfig.xml. | |
| * The default is an ErrorHandler that throws on error or fatalError and ignores warnings. | |
| * | |
| * @param handler the ErrorHandler instance to use | |
| * @since 2.1 | |
| */ | |
| public synchronized void registerErrorHandler(ErrorHandler handler) { | |
| errorHandler = handler; | |
| } | |
| /** | |
| * Return the registered instance of ErrorHandler. | |
| * | |
| * @return the registered ErrorHandler if registered; null otherwise | |
| * @since 2.1 | |
| */ | |
| public static ErrorHandler getRegisteredErrorHandler() { | |
| return errorHandler; | |
| } | |
| /** | |
| * Check that the parameter instance is of a class that is authorized for | |
| * JDOPermission("setStateManager"). This method is called by the replaceStateManager method in | |
| * persistence-capable classes. A class that is passed as the parameter to replaceStateManager | |
| * must be authorized for JDOPermission("setStateManager"). To improve performance, first the set | |
| * of authorized classes is checked, and if not present, a regular permission check is made. The | |
| * regular permission check requires that all callers on the stack, including the | |
| * persistence-capable class itself, must be authorized for JDOPermission("setStateManager"). | |
| * | |
| * @param sm an instance of StateManager whose class is to be checked. | |
| * @since 1.0.1 | |
| */ | |
| public static void checkAuthorizedStateManager(StateManager sm) { | |
| checkAuthorizedStateManagerClass(sm.getClass()); | |
| } | |
| /** | |
| * Check that the parameter instance is a class that is authorized for | |
| * JDOPermission("setStateManager"). This method is called by the constructors of JDO Reference | |
| * Implementation classes. | |
| * | |
| * @param smClass a Class to be checked for JDOPermission("setStateManager") | |
| * @since 1.0.1 | |
| */ | |
| public static void checkAuthorizedStateManagerClass(Class<?> smClass) { | |
| final SecurityManager scm = LegacyJava.getSecurityManager(); | |
| if (scm == null) { | |
| // if no security manager, no checking. | |
| return; | |
| } | |
| synchronized (authorizedStateManagerClasses) { | |
| if (authorizedStateManagerClasses.containsKey(smClass)) { | |
| return; | |
| } | |
| } | |
| // if not already authorized, perform "long" security checking. | |
| scm.checkPermission(JDOPermission.SET_STATE_MANAGER); | |
| } | |
| /** | |
| * Construct an instance of a key class using a String as input. This is a helper interface for | |
| * use with ObjectIdentity. Classes without a String constructor (such as those in java.lang and | |
| * java.util) will use this interface for constructing new instances. The result might be a | |
| * singleton or use some other strategy. | |
| */ | |
| public interface StringConstructor { | |
| /** | |
| * Construct an instance of the class for which this instance is registered. | |
| * | |
| * @param s the parameter for construction | |
| * @return the constructed object | |
| */ | |
| public Object construct(String s); | |
| } | |
| /** | |
| * Special StringConstructor instances for use with specific classes that have no public String | |
| * constructor. The Map is keyed on class instance and the value is an instance of | |
| * StringConstructor. | |
| */ | |
| static final Map<Class<?>, StringConstructor> stringConstructorMap = new HashMap<>(); | |
| /** | |
| * Register special StringConstructor instances. These instances are for constructing instances | |
| * from String parameters where there is no String constructor for them. | |
| * | |
| * @param cls the class to register a StringConstructor for | |
| * @param sc the StringConstructor instance | |
| * @return the previous StringConstructor registered for this class | |
| */ | |
| public Object registerStringConstructor(Class<?> cls, StringConstructor sc) { | |
| synchronized (stringConstructorMap) { | |
| return stringConstructorMap.put(cls, sc); | |
| } | |
| } | |
| /** Register the default special StringConstructor instances. */ | |
| static { | |
| if (isClassLoadable("java.util.Currency")) { | |
| jdoImplHelper.registerStringConstructor( | |
| Currency.class, | |
| s -> { | |
| try { | |
| return Currency.getInstance(s); | |
| } catch (IllegalArgumentException ex) { | |
| throw new JDOUserException( | |
| msg.msg( | |
| "EXC_CurrencyStringConstructorIllegalArgument", // NOI18N | |
| s), | |
| ex); | |
| } catch (Exception ex) { | |
| throw new JDOUserException( | |
| msg.msg("EXC_CurrencyStringConstructorException"), // NOI18N | |
| ex); | |
| } | |
| }); | |
| } | |
| jdoImplHelper.registerStringConstructor( | |
| Locale.class, | |
| s -> { | |
| try { | |
| return getLocale(s); | |
| } catch (Exception ex) { | |
| throw new JDOUserException( | |
| msg.msg("EXC_LocaleStringConstructorException"), ex); // NOI18N | |
| } | |
| }); | |
| jdoImplHelper.registerStringConstructor( | |
| Date.class, | |
| new StringConstructor() { | |
| public synchronized Object construct(String s) { | |
| try { | |
| // first, try the String as a Long | |
| return new Date(Long.parseLong(s)); | |
| } catch (NumberFormatException ex) { | |
| // not a Long; try the formatted date | |
| ParsePosition pp = new ParsePosition(0); | |
| Date result = dateFormat.parse(s, pp); | |
| if (result == null) { | |
| throw new JDOUserException( | |
| msg.msg( | |
| "EXC_DateStringConstructor", | |
| new Object[] // NOI18N | |
| {s, pp.getErrorIndex(), dateFormatPattern})); | |
| } | |
| return result; | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * Parse the String to a Locale. | |
| * | |
| * @param s the name of the Locale | |
| * @return the Locale corresponding to the name | |
| */ | |
| private static Locale getLocale(String s) { | |
| String lang = s; | |
| int firstUnderbar = s.indexOf('_'); | |
| if (firstUnderbar == -1) { | |
| // nothing but language | |
| return new Locale(lang); | |
| } | |
| lang = s.substring(0, firstUnderbar); | |
| String country; | |
| int secondUnderbar = s.indexOf('_', firstUnderbar + 1); | |
| if (secondUnderbar == -1) { | |
| // nothing but language, country | |
| country = s.substring(firstUnderbar + 1); | |
| return new Locale(lang, country); | |
| } | |
| country = s.substring(firstUnderbar + 1, secondUnderbar); | |
| String variant = s.substring(secondUnderbar + 1); | |
| return new Locale(lang, country, variant); | |
| } | |
| /** | |
| * Determine if a class is loadable in the current environment. | |
| * | |
| * @param className the fully-qualified name of the class | |
| * @return true if the class can be loaded; false otherwise | |
| */ | |
| private static boolean isClassLoadable(String className) { | |
| try { | |
| Class.forName(className); | |
| return true; | |
| } catch (ClassNotFoundException ex) { | |
| return false; | |
| } | |
| } | |
| /** Package prefixes of key classes always allowed for String construction. */ | |
| private static final String[] DEFAULT_ALLOWED_KEY_CLASS_PREFIXES = { | |
| "java.lang.", "java.math.", "java.time." // NOI18N | |
| }; | |
| /** Individual key classes always allowed for String construction. */ | |
| private static final Set<String> DEFAULT_ALLOWED_KEY_CLASS_NAMES = | |
| new HashSet<>( | |
| Arrays.asList( | |
| "java.util.Date", // NOI18N | |
| "java.util.Locale", // NOI18N | |
| "java.util.Currency", // NOI18N | |
| "java.util.UUID")); // NOI18N | |
| /** | |
| * Determine whether the given class may be instantiated from a String by {@link | |
| * #construct(String, String)}. A class is allowed if it belongs to the built-in set of value | |
| * classes or is named by the system property {@link | |
| * Constants#PROPERTY_ALLOWED_IDENTITY_KEY_CLASSES}. | |
| * | |
| * @param keyClass the candidate key class | |
| * @return true if the class may be constructed from a String | |
| */ | |
| private static boolean isIdentityKeyClassAllowed(Class<?> keyClass) { | |
| String name = keyClass.getName(); | |
| for (String prefix : DEFAULT_ALLOWED_KEY_CLASS_PREFIXES) { | |
| if (name.startsWith(prefix)) { | |
| return true; | |
| } | |
| } | |
| if (DEFAULT_ALLOWED_KEY_CLASS_NAMES.contains(name)) { | |
| return true; | |
| } | |
| String allowed = System.getProperty(Constants.PROPERTY_ALLOWED_IDENTITY_KEY_CLASSES); | |
| if (allowed != null) { | |
| for (String rawEntry : allowed.split(",")) { // NOI18N | |
| String entry = rawEntry.trim(); | |
| if (entry.isEmpty()) { | |
| continue; | |
| } | |
| if ("*".equals(entry)) { // NOI18N | |
| return true; | |
| } | |
| if (entry.endsWith(".*")) { // NOI18N | |
| if (name.startsWith(entry.substring(0, entry.length() - 1))) { | |
| return true; | |
| } | |
| } else if (entry.equals(name)) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Construct an instance of the parameter class, using the keyString as an argument to the | |
| * constructor. If the class has a StringConstructor instance registered, use it. If not, and the | |
| * class is an allowed identity key class, try to find a constructor for the class with a single | |
| * String argument. Otherwise, throw a JDOUserException. | |
| * | |
| * <p>Because the class name typically originates from the String form of an identity, which may | |
| * come from an untrusted source, only classes with a registered {@link StringConstructor}, the | |
| * built-in value classes, or classes named by the system property {@link | |
| * Constants#PROPERTY_ALLOWED_IDENTITY_KEY_CLASSES} are instantiated. The named class is loaded | |
| * without initializing it, so no code of a disallowed class runs. | |
| * | |
| * @param className the name of the class | |
| * @param keyString the String parameter for the constructor | |
| * @return the result of construction | |
| */ | |
| public static Object construct(String className, String keyString) { | |
| StringConstructor stringConstructor; | |
| try { | |
| // load without initializing: no static initializer of an unvetted class may run | |
| Class<?> keyClass = Class.forName(className, false, JDOImplHelper.class.getClassLoader()); | |
| synchronized (stringConstructorMap) { | |
| stringConstructor = stringConstructorMap.get(keyClass); | |
| } | |
| if (stringConstructor != null) { | |
| return stringConstructor.construct(keyString); | |
| } | |
| if (!isIdentityKeyClassAllowed(keyClass)) { | |
| throw new JDOUserException( | |
| msg.msg( | |
| "EXC_ObjectIdentityStringConstructionKeyClassNotAllowed", // NOI18N | |
| className, | |
| Constants.PROPERTY_ALLOWED_IDENTITY_KEY_CLASSES)); | |
| } | |
| Constructor<?> keyConstructor = keyClass.getConstructor(String.class); | |
| return keyConstructor.newInstance(keyString); | |
| } catch (JDOException ex) { | |
| throw ex; | |
| } catch (Exception ex) { | |
| /* ClassNotFoundException, | |
| NoSuchMethodException, | |
| InstantiationException, | |
| IllegalAccessException, | |
| InvocationTargetException */ | |
| throw new JDOUserException( | |
| msg.msg( | |
| "EXC_ObjectIdentityStringConstruction", // NOI18N | |
| new Object[] {ex.toString(), className, keyString}), | |
| ex); | |
| } | |
| } | |
| /** | |
| * Get the DateFormat instance for the default locale from the VM. This requires the following | |
| * privileges for JDOImplHelper in the security permissions file: permission | |
| * java.util.PropertyPermission "user.country", "read"; permission java.util.PropertyPermission | |
| * "user.timezone", "read,write"; permission java.util.PropertyPermission "java.home", "read"; If | |
| * these permissions are not present, or there is some other problem getting the default date | |
| * format, a simple formatter is returned. | |
| * | |
| * @since 2.1 | |
| * @return the default date-time formatter | |
| */ | |
| static DateFormat getDateTimeInstance() { | |
| DateFormat result = null; | |
| try { | |
| result = doPrivileged(() -> DateFormat.getDateTimeInstance()); | |
| } catch (Exception ex) { | |
| result = DateFormat.getInstance(); | |
| } | |
| return result; | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static <T> T doPrivileged(PrivilegedAction<T> privilegedAction) { | |
| try { | |
| return (T) LegacyJava.doPrivilegedAction.invoke(null, privilegedAction); | |
| } catch (IllegalAccessException | InvocationTargetException e) { | |
| if (e.getCause() instanceof RuntimeException) { | |
| throw (RuntimeException) e.getCause(); | |
| } | |
| throw new JDOFatalInternalException(e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Register a DateFormat instance for use with constructing Date instances. The default is the | |
| * default DateFormat instance. If the new instance implements SimpleDateFormat, get its pattern | |
| * for error messages. | |
| * | |
| * @since 2.0 | |
| * @param df the DateFormat instance to use | |
| */ | |
| public synchronized void registerDateFormat(DateFormat df) { | |
| dateFormat = df; | |
| if (df instanceof SimpleDateFormat) { | |
| dateFormatPattern = ((SimpleDateFormat) df).toPattern(); | |
| } else { | |
| dateFormatPattern = msg.msg("MSG_unknown"); // NOI18N | |
| } | |
| } | |
| /** | |
| * This is a helper class to manage metadata per persistence-capable class. The information is | |
| * used at runtime to provide field names and field types to the JDO Model. | |
| * | |
| * <p>This is the value of the <code>HashMap</code> which relates the <code> | |
| * PersistenceCapable Class</code> as a key to the metadata. | |
| */ | |
| static class Meta { | |
| /** | |
| * Construct an instance of <code>Meta</code>. | |
| * | |
| * @param fieldNames An array of <code>String</code> | |
| * @param fieldTypes An array of <code>Class</code> | |
| * @param fieldFlags an array of <code>int</code> | |
| * @param persistenceCapableSuperclass the most immediate <code>PersistenceCapable</code> | |
| * superclass | |
| * @param pc An instance of the <code>PersistenceCapable</code> class | |
| */ | |
| Meta( | |
| String[] fieldNames, | |
| Class<?>[] fieldTypes, | |
| byte[] fieldFlags, | |
| Class<?> persistenceCapableSuperclass, | |
| PersistenceCapable pc) { | |
| this.fieldNames = fieldNames; | |
| this.fieldTypes = fieldTypes; | |
| this.fieldFlags = fieldFlags; | |
| this.persistenceCapableSuperclass = persistenceCapableSuperclass; | |
| this.pc = pc; | |
| } | |
| /** | |
| * This is an array of field names used for the Model at runtime. The field is passed by the | |
| * static class initialization. | |
| */ | |
| final String[] fieldNames; | |
| /** | |
| * Get the field names from the metadata. | |
| * | |
| * @return the array of field names. | |
| */ | |
| String[] getFieldNames() { | |
| return fieldNames; | |
| } | |
| /** | |
| * This is an array of field types used for the Model at runtime. The field is passed by the | |
| * static class initialization. | |
| */ | |
| final Class<?>[] fieldTypes; | |
| /** | |
| * Get the field types from the metadata. | |
| * | |
| * @return the array of field types. | |
| */ | |
| Class<?>[] getFieldTypes() { | |
| return fieldTypes; | |
| } | |
| /** | |
| * This is an array of field flags used for the Model at runtime. The field is passed by the | |
| * static class initialization. | |
| */ | |
| final byte[] fieldFlags; | |
| /** | |
| * Get the field types from the metadata. | |
| * | |
| * @return the array of field types. | |
| */ | |
| byte[] getFieldFlags() { | |
| return fieldFlags; | |
| } | |
| /** | |
| * This is the <code>Class</code> instance of the <code>PersistenceCapable</code> superclass. | |
| */ | |
| final Class<?> persistenceCapableSuperclass; | |
| /** | |
| * Return the <code>PersistenceCapable</code> superclass. | |
| * | |
| * @return the <code>PersistenceCapable</code> superclass | |
| */ | |
| Class<?> getPersistenceCapableSuperclass() { | |
| return persistenceCapableSuperclass; | |
| } | |
| /** | |
| * This is an instance of <code>PersistenceCapable</code>, used at runtime to create new | |
| * instances. | |
| */ | |
| final PersistenceCapable pc; | |
| /** | |
| * Get an instance of the <code>PersistenceCapable</code> class. | |
| * | |
| * @return an instance of the <code>PersistenceCapable Class</code>. | |
| */ | |
| PersistenceCapable getPC() { | |
| return pc; | |
| } | |
| /** | |
| * Return the string form of the metadata. | |
| * | |
| * @return the string form | |
| */ | |
| public String toString() { | |
| return "Meta-" + pc.getClass().getName(); // NOI18N | |
| } | |
| } | |
| /** | |
| * Add a StateInterrogation to the list. Create a new list in case there is an iterator open on | |
| * the original list. | |
| * | |
| * @param si the StateInterrogation to add | |
| */ | |
| public synchronized void addStateInterrogation(StateInterrogation si) { | |
| List<StateInterrogation> newList = new ArrayList<>(stateInterrogations); | |
| newList.add(si); | |
| stateInterrogations = newList; | |
| } | |
| /** | |
| * Remove a StateInterrogation from the list. Create a new list in case there is an iterator open | |
| * on the original list. | |
| * | |
| * @param si the StateInterrogation to remove | |
| */ | |
| public synchronized void removeStateInterrogation(StateInterrogation si) { | |
| List<StateInterrogation> newList = new ArrayList<>(stateInterrogations); | |
| newList.remove(si); | |
| stateInterrogations = newList; | |
| } | |
| /** | |
| * Return an Iterator over all StateInterrogation instances. Synchronize to avoid | |
| * add/remove/iterate conflicts. | |
| * | |
| * @return an Iterator over all StateInterrogation instances. | |
| */ | |
| private synchronized Iterator<StateInterrogation> getStateInterrogationIterator() { | |
| return stateInterrogations.iterator(); | |
| } | |
| /** | |
| * Mark a non-binary-compatible instance dirty. Delegate to all registered StateInterrogation | |
| * instances until one of them handles the call. | |
| * | |
| * @param pc the instance to mark dirty | |
| * @param fieldName the field to mark dirty | |
| */ | |
| public void nonBinaryCompatibleMakeDirty(Object pc, String fieldName) { | |
| Iterator<StateInterrogation> sit = getStateInterrogationIterator(); | |
| while (sit.hasNext()) { | |
| StateInterrogation si = sit.next(); | |
| try { | |
| if (si.makeDirty(pc, fieldName)) return; | |
| } catch (Exception t) { | |
| // ignore exceptions from errant StateInterrogations | |
| } | |
| } | |
| } | |
| /** | |
| * Determine the state of a non-binary-compatible instance. Delegate to all registered | |
| * StateInterrogation instances until one of them handles the call (returns a non-null Boolean | |
| * with the answer). The caller provides the stateless "method object" that does the actual call | |
| * to the StateInterrogation instance. | |
| * | |
| * @param pc the instance to be checked | |
| * @param sibr the method object that delegates to the non-binary-compatible implementation | |
| * @return Boolean.TRUE if the instance satisfies the state interrogation; Boolean.FALSE if the | |
| * instance does not satisfy the interrogation; or null if the implementation does not manage | |
| * the class of the instance | |
| */ | |
| public boolean nonBinaryCompatibleIs(Object pc, StateInterrogationBooleanReturn sibr) { | |
| Iterator<StateInterrogation> sit = getStateInterrogationIterator(); | |
| while (sit.hasNext()) { | |
| StateInterrogation si = sit.next(); | |
| Boolean result; | |
| try { | |
| result = sibr.is(pc, si); | |
| } catch (Exception t) { | |
| continue; // ignore exceptions from errant StateInterrogations | |
| } | |
| if (result != null) return result.booleanValue(); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Return an object associated with a non-binary-compatible instance. Delegate to all registered | |
| * StateInterrogation instances until one of them handles the call (returns a non-null answer). | |
| * The caller provides the stateless "method object" that does the actual call to the | |
| * StateInterrogation instance. | |
| * | |
| * @param pc the instance whose associated object is needed | |
| * @param sibr the method object that delegates to the non-binary-compatible implementation | |
| * @return the associated object or null if the implementation does not manage the class of the | |
| * instance | |
| */ | |
| public Object nonBinaryCompatibleGet(Object pc, StateInterrogationObjectReturn sibr) { | |
| Iterator<StateInterrogation> sit = getStateInterrogationIterator(); | |
| while (sit.hasNext()) { | |
| StateInterrogation si = sit.next(); | |
| Object result; | |
| try { | |
| result = sibr.get(pc, si); | |
| } catch (Exception t) { | |
| continue; // ignore exceptions from errant StateInterrogations | |
| } | |
| if (result != null) return result; | |
| } | |
| return null; | |
| } | |
| /** | |
| * This is an interface used to interrogate the state of an instance that does not implement | |
| * PersistenceCapable. It is used for the methods that return a boolean value. | |
| */ | |
| public static interface StateInterrogationBooleanReturn { | |
| /** | |
| * Interrogate the state of the instance | |
| * | |
| * @param pc the instance | |
| * @param si the method object | |
| * @return the state of the instance or null | |
| */ | |
| public Boolean is(Object pc, StateInterrogation si); | |
| } | |
| /** | |
| * This is an interface used to interrogate the state of an instance that does not implement | |
| * PersistenceCapable. It is used for the methods that return an Object value. | |
| */ | |
| public static interface StateInterrogationObjectReturn { | |
| /** | |
| * Return the associated instance. | |
| * | |
| * @param pc the instance | |
| * @param si the method object | |
| * @return the associated object or null | |
| */ | |
| public Object get(Object pc, StateInterrogation si); | |
| } | |
| /** | |
| * Examines the given map for keys beginning with the JDO standard prefix, {@link | |
| * Constants#JAVAX_JDO_PREFIX}. If any property keys are found with that prefix but are unknown to | |
| * this version of the JDO standard, a JDOUserException is thrown with a message indicating the | |
| * unknown property. Keys that are not strings are ignored, as are string keys beginning with | |
| * {@link Constants#PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER} or not beginning with {@link | |
| * Constants#JAVAX_JDO_PREFIX}. | |
| * | |
| * @param properties The properties to examine. | |
| * @see Constants#JAVAX_JDO_PREFIX | |
| * @since 3.1 | |
| */ | |
| public static void assertOnlyKnownStandardProperties(Map<?, ?> properties) { | |
| if (properties == null || properties.isEmpty()) return; | |
| List<JDOUserException> exceptions = new ArrayList<>(); | |
| StringBuilder unknowns = new StringBuilder(); | |
| for (Object key : properties.keySet()) { | |
| if (!(key instanceof String)) { | |
| continue; // ignore keys not of type string | |
| } | |
| String s = ((String) key).toLowerCase(); // compare case-insensitively | |
| if (!s.startsWith(JAVAX_JDO_PREFIX_LOWER_CASED)) { | |
| continue; // ignore vendor-specific keys | |
| } | |
| if (s.startsWith(PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER_LOWER_CASED)) { | |
| continue; // ignore listener class names | |
| } | |
| if (!USER_CONFIGURABLE_STANDARD_PROPERTIES_LOWER_CASED.contains(s)) { | |
| exceptions.add(new JDOUserException(msg.msg("EXC_UnknownStandardProperty", s))); | |
| if (exceptions.size() > 1) { | |
| unknowns.append(","); | |
| } | |
| unknowns.append(s); | |
| } | |
| } | |
| if (exceptions.size() == 1) { | |
| throw exceptions.get(0); | |
| } else if (exceptions.size() > 1) { | |
| throw new JDOUserException( | |
| msg.msg("EXC_UnknownStandardProperties", unknowns.toString()), | |
| exceptions.toArray(new JDOUserException[] {})); | |
| } | |
| } | |
| } |