blob: 6eb9389774a167a12629e0786ed8aee0f0e2f321 [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
<<<<<<< Updated upstream
*
* 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
=======
*
* 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
>>>>>>> Stashed changes
* 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.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
/** 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_NullClass")); // 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_NullClass")); // 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_NullClass")); // 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;
}
}
/**
* 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, try to
* find a constructor for the class with a single String argument. Otherwise, throw a
* JDOUserException.
*
* @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 {
Class<?> keyClass = Class.forName(className);
synchronized (stringConstructorMap) {
stringConstructor = stringConstructorMap.get(keyClass);
}
if (stringConstructor != null) {
return stringConstructor.construct(keyString);
} else {
Constructor<?> keyConstructor = keyClass.getConstructor(new Class[] {String.class});
return keyConstructor.newInstance(new Object[] {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 (Throwable t) {
continue; // 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 (Throwable 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 (Throwable 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[] {}));
}
}
}