View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  /*
    19   * JDOImplHelper.java
    20   *
    21   */
    22
    23  package javax.jdo.spi;
    24
    25  import org.xml.sax.ErrorHandler;
    26
    27  import java.lang.reflect.Constructor;
    28
    29  import java.security.AccessController;
    30  import java.security.PrivilegedAction;
    31
    32  import java.text.DateFormat;
    33  import java.text.ParsePosition;
    34  import java.text.SimpleDateFormat;
    35
    36  import java.util.ArrayList;
    37  import java.util.Collection;
    38  import java.util.Collections;
    39  import java.util.Currency;
    40  import java.util.Date;
    41  import java.util.HashMap;
    42  import java.util.HashSet;
    43  import java.util.Iterator;
    44  import java.util.List;
    45  import java.util.Locale;
    46  import java.util.Map;
    47  import java.util.WeakHashMap;
    48
    49  import javax.jdo.JDOException;
    50  import javax.jdo.JDOFatalInternalException;
    51  import javax.jdo.JDOFatalUserException;
    52  import javax.jdo.JDOUserException;
    53  import javax.xml.parsers.DocumentBuilderFactory;
    54
    55  /** This class is a helper class for JDO implementations.  It contains methods
    56   * to register metadata for persistence-capable classes and to perform common
    57   * operations needed by implementations, not by end users.
    58   * <P><code>JDOImplHelper</code> allows construction of instances of
    59   * persistence-capable classes without using reflection.
    60   * <P>Persistence-capable classes register themselves via a static method
    61   * at class load time.
    62   * There is no security restriction on this access.  JDO implementations
    63   * get access to the functions provided by this class only if they are
    64   * authorized by the security manager.  To avoid having every call go through
    65   * the security manager, only the call to get an instance is checked.  Once an
    66   * implementation
    67   * has an instance, any of the methods can be invoked without security checks.
    68   * @version 2.1
    69   *
    70   */
    71  public class JDOImplHelper extends java.lang.Object {
    72
    73      /** This synchronized <code>HashMap</code> contains a static mapping of
    74       * <code>PersistenceCapable</code> class to
    75       * metadata for the class used for constructing new instances.  New entries
    76       * are added by the static method in each <code>PersistenceCapable</code>
    77       * class.  Entries are never removed.
    78       */
    79      private static Map<Class,Meta> registeredClasses =
    80              Collections.synchronizedMap(new HashMap<Class,Meta> ());
    81
    82      /** This Set contains all classes that have registered for setStateManager
    83       * permissions via authorizeStateManagerClass.
    84       * Only the key is used in order to maintain a weak set of classes.
    85       */
    86      private static final Map<Class,Class>
    87              authorizedStateManagerClasses = new WeakHashMap<Class,Class>();
    88
    89      /** This list contains the registered listeners for
    90       * <code>RegisterClassEvent</code>s.
    91       */
    92      private static final List<RegisterClassListener>
    93              listeners = new ArrayList<RegisterClassListener>();
    94
    95      /** The list of registered StateInterrogation instances
    96       */
    97      private static List<StateInterrogation>
    98              stateInterrogations = new ArrayList<StateInterrogation>();
    99
    100     /** The singleton <code>JDOImplHelper</code> instance.
    101      */
    102     private static JDOImplHelper jdoImplHelper = new JDOImplHelper();
    103
    104     /** The Internationalization message helper.
    105      */
    106     private final static I18NHelper msg =
    107             I18NHelper.getInstance ("javax.jdo.Bundle"); //NOI18N
    108
    109     /** The DateFormat pattern.
    110      */
    111     private static String dateFormatPattern;
    112
    113     /** The default DateFormat instance.
    114      */
    115     private static DateFormat dateFormat;
    116
    117     /**
    118      * The DocumentBuilderFactory used during jdoconfig.xml parsing.
    119      */
    120     private static DocumentBuilderFactory documentBuilderFactory;
    121
    122     /**
    123      * The ErrorHandler used during jdoconfig.xml parsing.
    124      */
    125     private static ErrorHandler errorHandler;
    126
    127     /** Register the default DateFormat instance.
    128      */
    129     static {
    130         jdoImplHelper.registerDateFormat(getDateTimeInstance());
    131     }
    132
    133     /** Creates new JDOImplHelper */
    134     private JDOImplHelper() {
    135     }
    136
    137     /** Get an instance of <code>JDOImplHelper</code>.  This method
    138      * checks that the caller is authorized for
    139      * <code>JDOPermission("getMetadata")</code>, and if not, throws
    140      * <code>SecurityException</code>.
    141      * @return an instance of <code>JDOImplHelper</code>.
    142      * @throws SecurityException if the caller is not authorized for
    143      * JDOPermission("getMetadata").
    144      */
    145     public static JDOImplHelper getInstance()
    146         throws SecurityException {
    147         SecurityManager sec = System.getSecurityManager();
    148         if (sec != null) {
    149             // throws exception if caller is not authorized
    150             sec.checkPermission (JDOPermission.GET_METADATA);
    151         }
    152         return jdoImplHelper;
    153     }
    154
    155     /** Get the field names for a <code>PersistenceCapable</code> class.  The
    156      * order of fields is the natural ordering of the <code>String</code> class
    157      * (without considering localization).
    158      * @param pcClass the <code>PersistenceCapable</code> class.
    159      * @return the field names for the class.
    160      */
    161     public String[] getFieldNames (Class pcClass) {
    162         Meta meta = getMeta (pcClass);
    163         return meta.getFieldNames();
    164     }
    165
    166     /** Get the field types for a <code>PersistenceCapable</code> class.  The
    167      * order of fields is the same as for field names.
    168      * @param pcClass the <code>PersistenceCapable</code> class.
    169      * @return the field types for the class.
    170      */
    171     public Class[] getFieldTypes (Class pcClass) {
    172         Meta meta = getMeta (pcClass);
    173         return meta.getFieldTypes();
    174     }
    175
    176     /** Get the field flags for a <code>PersistenceCapable</code> class.  The
    177      * order of fields is the same as for field names.
    178      * @param pcClass the <code>PersistenceCapable</code> class.
    179      * @return the field types for the class.
    180      */
    181     public byte[] getFieldFlags (Class pcClass) {
    182         Meta meta = getMeta (pcClass);
    183         return meta.getFieldFlags();
    184     }
    185
    186     /** Get the persistence-capable superclass for a
    187      * <code>PersistenceCapable</code> class.
    188      * @param pcClass the <code>PersistenceCapable</code> class.
    189      * @return The <code>PersistenceCapable</code> superclass for this class,
    190      * or <code>null</code> if there isn't one.
    191      */
    192     public Class getPersistenceCapableSuperclass (Class pcClass) {
    193         Meta meta = getMeta (pcClass);
    194         return meta.getPersistenceCapableSuperclass();
    195     }
    196
    197
    198     /** Create a new instance of the class and assign its
    199      * <code>jdoStateManager</code>.  The new instance has its
    200      * <code>jdoFlags</code> set to <code>LOAD_REQUIRED</code>.
    201      * @see PersistenceCapable#jdoNewInstance(StateManager sm)
    202      * @param pcClass the <code>PersistenceCapable</code> class.
    203      * @param sm the <code>StateManager</code> which will own the new instance.
    204      * @return the new instance, or <code>null</code> if the class is not
    205      * registered.
    206      */
    207     public PersistenceCapable newInstance (Class pcClass, StateManager sm) {
    208         Meta meta = getMeta (pcClass);
    209         PersistenceCapable pcInstance = meta.getPC();
    210         return pcInstance == null?null:pcInstance.jdoNewInstance(sm);
    211     }
    212
    213     /** Create a new instance of the class and assign its
    214      * <code>jdoStateManager</code> and key values from the ObjectId.  If the
    215      * oid parameter is <code>null</code>, no key values are copied.
    216      * The new instance has its <code>jdoFlags</code> set to
    217      * <code>LOAD_REQUIRED</code>.
    218      * @see PersistenceCapable#jdoNewInstance(StateManager sm, Object oid)
    219      * @param pcClass the <code>PersistenceCapable</code> class.
    220      * @param sm the <code>StateManager</code> which will own the new instance.
    221      * @return the new instance, or <code>null</code> if the class is not
    222      * registered.
    223      * @param oid the ObjectId instance from which to copy key field values.
    224  */
    225     public PersistenceCapable newInstance
    226             (Class pcClass, StateManager sm, Object oid) {
    227         Meta meta = getMeta (pcClass);
    228         PersistenceCapable pcInstance = meta.getPC();
    229         return pcInstance == null?null:pcInstance.jdoNewInstance(sm, oid);
    230     }
    231
    232     /** Create a new instance of the ObjectId class of this
    233      * <code>PersistenceCapable</code> class.
    234      * It is intended only for application identity. This method should
    235      * not be called for classes that use single field identity;
    236      * newObjectIdInstance(Class, Object) should be used instead.
    237      * If the class has been
    238      * enhanced for datastore identity, or if the class is abstract,
    239      * null is returned.
    240      * @param pcClass the <code>PersistenceCapable</code> class.
    241      * @return the new ObjectId instance, or <code>null</code> if the class
    242      * is not registered.
    243      */
    244     public Object newObjectIdInstance (Class pcClass) {
    245         Meta meta = getMeta (pcClass);
    246         PersistenceCapable pcInstance = meta.getPC();
    247         return pcInstance == null?null:pcInstance.jdoNewObjectIdInstance();
    248     }
    249
    250     /** Create a new instance of the class used by the parameter Class
    251      * for JDO identity, using the
    252      * key constructor of the object id class. It is intended for single
    253      * field identity. The identity
    254      * instance returned has no relationship with the values of the primary key
    255      * fields of the persistence-capable instance on which the method is called.
    256      * If the key is the wrong class for the object id class, null is returned.
    257      * <P>For classes that use single field identity, if the parameter is
    258      * of one of the following types, the behavior must be as specified:
    259      * <ul><li><code>Number</code> or <code>Character</code>: the
    260      * parameter must be the single field
    261      * type or the wrapper class of the primitive field type; the parameter
    262      * is passed to the single field identity constructor
    263      * </li><li><code>ObjectIdFieldSupplier</code>: the field value
    264      * is fetched from the <code>ObjectIdFieldSupplier</code> and passed to the
    265      * single field identity constructor
    266      * </li><li><code>String</code>: the String is passed to the
    267      * single field identity constructor
    268      * </li></ul>
    269      * @return the new ObjectId instance, or <code>null</code>
    270      * if the class is not registered.
    271      * @param obj the <code>Object</code> form of the object id
    272      * @param pcClass the <code>PersistenceCapable</code> class.
    273      * @since 2.0
    274      */
    275     public Object newObjectIdInstance (Class pcClass, Object obj) {
    276         Meta meta = getMeta (pcClass);
    277         PersistenceCapable pcInstance = meta.getPC();
    278         return (pcInstance == null)?null:pcInstance.jdoNewObjectIdInstance(obj);
    279     }
    280
    281     /** Copy fields from an outside source to the key fields in the ObjectId.
    282      * This method is generated in the <code>PersistenceCapable</code> class to
    283      * generate a call to the field manager for each key field in the ObjectId.
    284      * <P>For example, an ObjectId class that has three key fields
    285      * (<code>int id</code>, <code>String name</code>, and
    286      * <code>Float salary</code>) would have the method generated:
    287      * <P><code>
    288      * void jdoCopyKeyFieldsToObjectId (Object oid, ObjectIdFieldSupplier fm) {
    289      * <BR>    oid.id = fm.fetchIntField (0);
    290      * <BR>    oid.name = fm.fetchStringField (1);
    291      * <BR>    oid.salary = fm.fetchObjectField (2);
    292      * <BR>}</code>
    293      * <P>The implementation is responsible for implementing the
    294      * <code>ObjectIdFieldSupplier</code> to provide the values for the key
    295      * fields.
    296      * @param pcClass the <code>PersistenceCapable Class</code>.
    297      * @param oid the ObjectId target of the copy.
    298      * @param fm the field manager that supplies the field values.
    299  */
    300     public void copyKeyFieldsToObjectId
    301     (Class pcClass, PersistenceCapable.ObjectIdFieldSupplier fm, Object oid) {
    302         Meta meta = getMeta (pcClass);
    303         PersistenceCapable pcInstance = meta.getPC();
    304         if (pcInstance == null) {
    305             throw new JDOFatalInternalException (msg.msg(
    306                     "ERR_AbstractClassNoIdentity", pcClass.getName())); //NOI18N
    307         }
    308         pcInstance.jdoCopyKeyFieldsToObjectId(fm, oid);
    309     }
    310
    311     /** Copy fields to an outside source from the key fields in the ObjectId.
    312      * This method is generated in the <code>PersistenceCapable</code> class to
    313      * generate a call to the field manager for each key field in the ObjectId.
    314      * For example, an ObjectId class that has three key fields
    315      * (<code>int id</code>, <code>String name</code>, and
    316      * <code>Float salary</code>) would have the method generated:
    317      * <P><code>void jdoCopyKeyFieldsFromObjectId
    318      * <BR>        (PersistenceCapable oid, ObjectIdFieldConsumer fm) {
    319      * <BR>     fm.storeIntField (0, oid.id);
    320      * <BR>     fm.storeStringField (1, oid.name);
    321      * <BR>     fm.storeObjectField (2, oid.salary);
    322      * <BR>}</code>
    323      * <P>The implementation is responsible for implementing the
    324      * <code>ObjectIdFieldConsumer</code> to store the values for the key
    325      * fields.
    326      * @param pcClass the <code>PersistenceCapable</code> class
    327      * @param oid the ObjectId source of the copy.
    328      * @param fm the field manager that receives the field values.
    329      */
    330     public void copyKeyFieldsFromObjectId
    331     (Class pcClass, PersistenceCapable.ObjectIdFieldConsumer fm, Object oid) {
    332         Meta meta = getMeta (pcClass);
    333         PersistenceCapable pcInstance = meta.getPC();
    334         if (pcInstance == null) {
    335             throw new JDOFatalInternalException (msg.msg(
    336                     "ERR_AbstractClassNoIdentity", pcClass.getName())); //NOI18N
    337         }
    338         pcInstance.jdoCopyKeyFieldsFromObjectId(fm, oid);
    339     }
    340
    341     /** Register metadata by class.  The registration will be done in the
    342      * class named <code>JDOImplHelper</code> loaded by the same or an
    343      * ancestor class loader as the <code>PersistenceCapable</code> class
    344      * performing the registration.
    345      *
    346      * @param pcClass the <code>PersistenceCapable</code> class
    347      * used as the key for lookup.
    348      * @param fieldNames an array of <code>String</code> field names for
    349      * persistent and transactional fields
    350      * @param fieldTypes an array of <code>Class</code> field types
    351      * @param fieldFlags the Field Flags for persistent and transactional fields
    352      * @param pc an instance of the <code>PersistenceCapable</code> class
    353      * @param persistenceCapableSuperclass the most immediate superclass that is
    354      * <code>PersistenceCapable</code>
    355      */
    356     public static void registerClass (Class pcClass,
    357             String[] fieldNames, Class[] fieldTypes,
    358             byte[] fieldFlags, Class persistenceCapableSuperclass,
    359             PersistenceCapable pc) {
    360         if (pcClass == null)
    361             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
    362         Meta meta = new Meta (fieldNames, fieldTypes,
    363             fieldFlags, persistenceCapableSuperclass, pc);
    364         registeredClasses.put (pcClass, meta);
    365
    366         // handle class registration listeners
    367         synchronized (listeners) {
    368             if (!listeners.isEmpty()) {
    369                 RegisterClassEvent event = new RegisterClassEvent(
    370                     jdoImplHelper, pcClass, fieldNames, fieldTypes,
    371                     fieldFlags, persistenceCapableSuperclass);
    372                 for (Iterator i = listeners.iterator(); i.hasNext();) {
    373                     RegisterClassListener crl =
    374                         (RegisterClassListener)i.next();
    375                     if (crl != null) {
    376                         crl.registerClass(event);
    377                     }
    378                 }
    379             }
    380         }
    381     }
    382
    383     /**
    384      * Unregister metadata by class loader. This method unregisters all
    385      * registered <code>PersistenceCapable</code> classes loaded by the
    386      * specified class loader. Any attempt to get metadata for unregistered
    387      * classes will result in a <code>JDOFatalUserException</code>.
    388      * @param cl the class loader.
    389      * @since 1.0.2
    390      */
    391     public void unregisterClasses (ClassLoader cl)
    392     {
    393         SecurityManager sec = System.getSecurityManager();
    394         if (sec != null) {
    395             // throws exception if caller is not authorized
    396             sec.checkPermission (JDOPermission.MANAGE_METADATA);
    397         }
    398         synchronized(registeredClasses) {
    399             for (Iterator i = registeredClasses.keySet().iterator();
    400                  i.hasNext();) {
    401                 Class pcClass = (Class)i.next();
    402                 // Note, the pc class was registered by calling the static
    403                 // method JDOImplHelper.registerClass. This means the
    404                 // JDOImplHelper class loader is the same as or an ancestor
    405                 // of the class loader of the pc class. In this case method
    406                 // getClassLoader does not perform a security check for
    407                 // RuntimePermission("getClassLoader") and thus we do not
    408                 // need a privileged block for the getClassLoader call.
    409                 if ((pcClass != null) && (pcClass.getClassLoader() == cl)) {
    410                     // unregister pc class, if its class loader is the
    411                     // specified one.
    412                     i.remove();
    413                 }
    414             }
    415         }
    416     }
    417
    418     /**
    419      * Unregister metadata by class. This method unregisters the specified
    420      * class. Any further attempt to get metadata for the specified class will
    421      * result in a <code>JDOFatalUserException</code>.
    422      * @param pcClass the <code>PersistenceCapable</code> class to be
    423      * unregistered.
    424      * @since 1.0.2
    425      */
    426     public void unregisterClass (Class pcClass)
    427     {
    428         if (pcClass == null)
    429             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
    430         SecurityManager sec = System.getSecurityManager();
    431         if (sec != null) {
    432             // throws exception if caller is not authorized
    433             sec.checkPermission (JDOPermission.MANAGE_METADATA);
    434         }
    435         registeredClasses.remove(pcClass);
    436     }
    437
    438     /**
    439      * Add the specified <code>RegisterClassListener</code> to the listener
    440      * list.
    441      * @param crl the listener to be added
    442      */
    443     public void addRegisterClassListener (RegisterClassListener crl) {
    444         HashSet alreadyRegisteredClasses = null;
    445         synchronized (listeners) {
    446             listeners.add(crl);
    447             // Make a copy of the existing set of registered classes.
    448             // Between these two lines of code, any number of new class
    449             // registrations might occur, and will then all wait until this
    450             // synchronized block completes. Some of the class registrations
    451             // might be delivered twice to the newly registered listener.
    452             alreadyRegisteredClasses = new HashSet<Class> (registeredClasses.keySet());
    453         }
    454         // new registrations will call the new listener while the following
    455         // occurs notify the new listener about already-registered classes
    456         for (Iterator it = alreadyRegisteredClasses.iterator(); it.hasNext();) {
    457             Class pcClass = (Class)it.next();
    458             Meta meta = getMeta (pcClass);
    459             RegisterClassEvent event = new RegisterClassEvent(
    460                 this, pcClass, meta.getFieldNames(), meta.getFieldTypes(),
    461                 meta.getFieldFlags(), meta.getPersistenceCapableSuperclass());
    462             crl.registerClass (event);
    463         }
    464     }
    465
    466     /**
    467      * Remove the specified <code>RegisterClassListener</code> from the listener
    468      * list.
    469      * @param crl the listener to be removed
    470      */
    471     public void removeRegisterClassListener (RegisterClassListener crl) {
    472         synchronized (listeners) {
    473             listeners.remove(crl);
    474         }
    475     }
    476
    477     /**
    478      * Returns a collection of class objects of the registered
    479      * persistence-capable classes.
    480      * @return registered persistence-capable classes
    481      */
    482     public Collection<Class> getRegisteredClasses() {
    483         return Collections.unmodifiableCollection(registeredClasses.keySet());
    484     }
    485
    486     /** Look up the metadata for a <code>PersistenceCapable</code> class.
    487      * @param pcClass the <code>Class</code>.
    488      * @return the <code>Meta</code> for the <code>Class</code>.
    489      */
    490     private static Meta getMeta (Class pcClass) {
    491         Meta ret = (Meta) registeredClasses.get (pcClass);
    492         if (ret == null) {
    493             throw new JDOFatalUserException(
    494                 msg.msg ("ERR_NoMetadata", pcClass.getName())); //NOI18N
    495         }
    496         return ret;
    497     }
    498
    499     /** Register a class authorized to replaceStateManager.  The caller of
    500      * this method must be authorized for JDOPermission("setStateManager").
    501      * During replaceStateManager, a persistence-capable class will call
    502      * the corresponding checkAuthorizedStateManager and the class of the
    503      * instance of the parameter must have been registered.
    504      * @param smClass a Class that is authorized for
    505      * JDOPermission("setStateManager").
    506      * @throws SecurityException if the caller is not authorized for
    507      * JDOPermission("setStateManager").
    508      * @since 1.0.1
    509      */
    510     public static void registerAuthorizedStateManagerClass (Class smClass)
    511         throws SecurityException {
    512         if (smClass == null)
    513             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
    514         SecurityManager sm = System.getSecurityManager();
    515         if (sm != null) {
    516             sm.checkPermission(JDOPermission.SET_STATE_MANAGER);
    517         }
    518         synchronized (authorizedStateManagerClasses) {
    519             authorizedStateManagerClasses.put(smClass, null);
    520         }
    521     }
    522
    523     /** Register classes authorized to replaceStateManager.  The caller of
    524      * this method must be authorized for JDOPermission("setStateManager").
    525      * During replaceStateManager, a persistence-capable class will call
    526      * the corresponding checkAuthorizedStateManager and the class of the
    527      * instance of the parameter must have been registered.
    528      * @param smClasses a Collection of Classes that are authorized for
    529      * JDOPermission("setStateManager").
    530      * @throws SecurityException if the caller is not authorized for
    531      * JDOPermission("setStateManager").
    532      * @since 1.0.1
    533      */
    534     public static void registerAuthorizedStateManagerClasses (
    535             Collection smClasses) throws SecurityException {
    536         SecurityManager sm = System.getSecurityManager();
    537         if (sm != null) {
    538             sm.checkPermission(JDOPermission.SET_STATE_MANAGER);
    539             synchronized (authorizedStateManagerClasses) {
    540                 for (Iterator it = smClasses.iterator(); it.hasNext();) {
    541                     Object smClass = it.next();
    542                     if (!(smClass instanceof Class)) {
    543                         throw new ClassCastException(
    544                             msg.msg("ERR_StateManagerClassCast", //NOI18N
    545                                 smClass.getClass().getName()));
    546                     }
    547                     registerAuthorizedStateManagerClass((Class)it.next());
    548                 }
    549             }
    550         }
    551     }
    552
    553     /**
    554      * Register a DocumentBuilderFactory instance for use in parsing the
    555      * resource(s) META-INF/jdoconfig.xml. The default is governed by the
    556      * semantics of DocumentBuilderFactory.newInstance().
    557      *
    558      * @param factory the DocumentBuilderFactory instance to use
    559      * @since 2.1
    560      */
    561     public synchronized void registerDocumentBuilderFactory(
    562             DocumentBuilderFactory factory) {
    563         documentBuilderFactory = factory;
    564     }
    565
    566     /**
    567      * Return the registered instance of DocumentBuilderFactory.
    568      * @return the DocumentBuilderFactory if registered; null otherwise
    569      * @since 2.1
    570      */
    571     public static DocumentBuilderFactory getRegisteredDocumentBuilderFactory() {
    572         return documentBuilderFactory;
    573     }
    574
    575     /**
    576      * Register an ErrorHandler instance for use in parsing the
    577      * resource(s) META-INF/jdoconfig.xml. The default is an ErrorHandler
    578      * that throws on error or fatalError and ignores warnings.
    579      *
    580      * @param handler the ErrorHandler instance to use
    581      * @since 2.1
    582      */
    583     public synchronized void registerErrorHandler(ErrorHandler handler) {
    584         errorHandler = handler;
    585     }
    586
    587     /**
    588      * Return the registered instance of ErrorHandler.
    589      * @return the registered ErrorHandler if registered; null otherwise
    590      * @since 2.1
    591      */
    592     public static ErrorHandler getRegisteredErrorHandler() {
    593         return errorHandler;
    594     }
    595
    596     /** Check that the parameter instance is of a class that is authorized for
    597      * JDOPermission("setStateManager").  This method is called by the
    598      * replaceStateManager method in persistence-capable classes.
    599      * A class that is passed as the parameter to replaceStateManager must be
    600      * authorized for JDOPermission("setStateManager").  To improve performance,
    601      * first the set of authorized classes is checked, and if not present, a
    602      * regular permission check is made.  The regular permission check requires
    603      * that all callers on the stack, including the persistence-capable class
    604      * itself, must be authorized for JDOPermission("setStateManager").
    605      * @param sm an instance of StateManager whose class is to be checked.
    606      * @since 1.0.1
    607      */
    608     public static void checkAuthorizedStateManager (StateManager sm) {
    609         checkAuthorizedStateManagerClass(sm.getClass());
    610     }
    611
    612     /** Check that the parameter instance is a class that is authorized for
    613      * JDOPermission("setStateManager").  This method is called by the
    614      * constructors of JDO Reference Implementation classes.
    615      * @param smClass a Class to be checked for JDOPermission("setStateManager")
    616      * @since 1.0.1
    617      */
    618     public static void checkAuthorizedStateManagerClass (Class smClass) {
    619         final SecurityManager scm = System.getSecurityManager();
    620         if (scm == null) {
    621             // if no security manager, no checking.
    622             return;
    623         }
    624         synchronized(authorizedStateManagerClasses) {
    625             if (authorizedStateManagerClasses.containsKey(smClass)) {
    626                 return;
    627             }
    628         }
    629         // if not already authorized, perform "long" security checking.
    630         scm.checkPermission(JDOPermission.SET_STATE_MANAGER);
    631     }
    632
    633     /**
    634      * Construct an instance of a key class using a String as input.
    635      * This is a helper interface for use with ObjectIdentity.
    636      * Classes without a String constructor (such as those in java.lang
    637      * and java.util) will use this interface for constructing new instances.
    638      * The result might be a singleton or use some other strategy.
    639      */
    640     public interface StringConstructor {
    641         /**
    642          * Construct an instance of the class for which this instance
    643          * is registered.
    644          * @param s the parameter for construction
    645          * @return the constructed object
    646          */
    647         public Object construct(String s);
    648     }
    649
    650     /**
    651      * Special StringConstructor instances for use with specific
    652      * classes that have no public String constructor. The Map is
    653      * keyed on class instance and the value is an instance of
    654      * StringConstructor.
    655      */
    656     static final Map<Class,StringConstructor> stringConstructorMap =
    657             new HashMap<Class,StringConstructor>();
    658
    659     /**
    660      *
    661      * Register special StringConstructor instances. These instances
    662      * are for constructing instances from String parameters where there
    663      * is no String constructor for them.
    664      * @param cls the class to register a StringConstructor for
    665      * @param sc the StringConstructor instance
    666      * @return the previous StringConstructor registered for this class
    667      */
    668     public Object registerStringConstructor(Class cls, StringConstructor sc) {
    669         synchronized(stringConstructorMap) {
    670             return stringConstructorMap.put(cls, sc);
    671         }
    672     }
    673
    674     /** Register the default special StringConstructor instances.
    675      */
    676     static {
    677         if (isClassLoadable("java.util.Currency")) {
    678             jdoImplHelper.registerStringConstructor(
    679                     Currency.class, new StringConstructor() {
    680                 public Object construct(String s) {
    681                     try {
    682                         return Currency.getInstance(s);
    683                     } catch (IllegalArgumentException ex) {
    684                         throw new javax.jdo.JDOUserException(msg.msg(
    685                             "EXC_CurrencyStringConstructorIllegalArgument", //NOI18N
    686                             s), ex);
    687                     } catch (Exception ex) {
    688                         throw new JDOUserException(msg.msg(
    689                             "EXC_CurrencyStringConstructorException"), //NOI18N
    690                             ex);
    691                     }
    692                 }
    693             });
    694         }
    695         jdoImplHelper.registerStringConstructor(Locale.class, new StringConstructor() {
    696             public Object construct(String s) {
    697                 try {
    698                     return getLocale(s);
    699                 } catch (Exception ex) {
    700                     throw new JDOUserException(msg.msg(
    701                         "EXC_LocaleStringConstructorException"), ex); //NOI18N
    702                 }
    703             }
    704         });
    705         jdoImplHelper.registerStringConstructor(Date.class, new StringConstructor() {
    706             public synchronized Object construct(String s) {
    707                 try {
    708                     // first, try the String as a Long
    709                     return new Date(Long.parseLong(s));
    710                 } catch (NumberFormatException ex) {
    711                     // not a Long; try the formatted date
    712                     ParsePosition pp = new ParsePosition(0);
    713                     Date result = dateFormat.parse(s, pp);
    714                     if (result == null) {
    715                         throw new JDOUserException (msg.msg(
    716                             "EXC_DateStringConstructor", new Object[] //NOI18N
    717                             {s, new Integer(pp.getErrorIndex()),
    718                              dateFormatPattern}));
    719                     }
    720                     return result;
    721                 }
    722             }
    723         });
    724     }
    725
    726     /**
    727      * Parse the String to a Locale.
    728      * @param s the name of the Locale
    729      * @return the Locale corresponding to the name
    730      */
    731     private static Locale getLocale(String s) {
    732         String lang = s;
    733         int firstUnderbar = s.indexOf('_');
    734         if (firstUnderbar == -1) {
    735             // nothing but language
    736             return new Locale(lang);
    737         }
    738         lang = s.substring(0, firstUnderbar);
    739         String country;
    740         int secondUnderbar = s.indexOf('_', firstUnderbar + 1);
    741         if (secondUnderbar == -1) {
    742             // nothing but language, country
    743             country = s.substring(firstUnderbar + 1);
    744             return new Locale(lang, country);
    745         }
    746         country = s.substring(firstUnderbar + 1, secondUnderbar);
    747         String variant = s.substring(secondUnderbar + 1);
    748         return new Locale(lang, country, variant);
    749     }
    750     /**
    751      * Determine if a class is loadable in the current environment.
    752      * @param className the fully-qualified name of the class
    753      * @return true if the class can be loaded; false otherwise
    754      */
    755     private static boolean isClassLoadable(String className) {
    756         try {
    757             Class.forName(className);
    758             return true;
    759         } catch (ClassNotFoundException ex) {
    760             return false;
    761         }
    762     }
    763
    764     /**
    765      * Construct an instance of the parameter class, using the keyString
    766      * as an argument to the constructor. If the class has a StringConstructor
    767      * instance registered, use it. If not, try to find a constructor for
    768      * the class with a single String argument. Otherwise, throw a
    769      * JDOUserException.
    770      * @param className the name of the class
    771      * @param keyString the String parameter for the constructor
    772      * @return the result of construction
    773      */
    774     public static Object construct(String className, String keyString) {
    775         StringConstructor stringConstructor;
    776         try {
    777             Class keyClass = Class.forName(className);
    778             synchronized(stringConstructorMap) {
    779                 stringConstructor =
    780                         (StringConstructor) stringConstructorMap.get(keyClass);
    781             }
    782             if (stringConstructor != null) {
    783                 return stringConstructor.construct(keyString);
    784             } else {
    785                 Constructor keyConstructor =
    786                     keyClass.getConstructor(new Class[]{String.class});
    787                 return keyConstructor.newInstance(new Object[]{keyString});
    788             }
    789         } catch (JDOException ex) {
    790             throw ex;
    791         } catch (Exception ex) {
    792              /* ClassNotFoundException,
    793                 NoSuchMethodException,
    794                 InstantiationException,
    795                 IllegalAccessException,
    796                 InvocationTargetException */
    797             throw new JDOUserException(
    798                 msg.msg("EXC_ObjectIdentityStringConstruction",  //NOI18N
    799                 new Object[] {ex.toString(), className, keyString}), ex);
    800         }
    801     }
    802
    803     /**
    804      * Get the DateFormat instance for the default locale from the VM.
    805      * This requires the following privileges for JDOImplHelper in the
    806      * security permissions file:
    807      * permission java.util.PropertyPermission "user.country", "read";
    808      * permission java.util.PropertyPermission "user.timezone", "read,write";
    809      * permission java.util.PropertyPermission "java.home", "read";
    810      * If these permissions are not present, or there is some other
    811      * problem getting the default date format, a simple formatter is returned.
    812      * @since 2.1
    813      * @return the default date-time formatter
    814      */
    815     static DateFormat getDateTimeInstance() {
    816         DateFormat result = null;
    817         try {
    818         result = AccessController.doPrivileged (
    819             new PrivilegedAction<DateFormat> () {
    820                 public DateFormat run () {
    821                     return DateFormat.getDateTimeInstance();
    822                 }
    823             }
    824             );
    825         } catch (Exception ex) {
    826             result = DateFormat.getInstance();
    827         }
    828         return result;
    829     }
    830
    831     /**
    832      * Register a DateFormat instance for use with constructing Date
    833      * instances. The default is the default DateFormat instance.
    834      * If the new instance implements SimpleDateFormat, get its pattern
    835      * for error messages.
    836      * @since 2.0
    837      * @param df the DateFormat instance to use
    838      */
    839     public synchronized void registerDateFormat(DateFormat df) {
    840         dateFormat = df;
    841         if (df instanceof SimpleDateFormat) {
    842             dateFormatPattern = ((SimpleDateFormat)df).toPattern();
    843         } else {
    844             dateFormatPattern = msg.msg("MSG_unknown"); //NOI18N
    845         }
    846     }
    847
    848     /** This is a helper class to manage metadata per persistence-capable
    849      * class.  The information is used at runtime to provide field names and
    850      * field types to the JDO Model.
    851      *
    852      * This is the value of the <code>HashMap</code> which
    853      * relates the <code>PersistenceCapable Class</code>
    854      * as a key to the metadata.
    855      */
    856     static class Meta {
    857
    858         /** Construct an instance of <code>Meta</code>.
    859          * @param fieldNames An array of <code>String</code>
    860          * @param fieldTypes An array of <code>Class</code>
    861          * @param fieldFlags an array of <code>int</code>
    862          * @param persistenceCapableSuperclass the most immediate
    863          * <code>PersistenceCapable</code> superclass
    864          * @param pc An instance of the <code>PersistenceCapable</code> class
    865          */
    866         Meta (String[] fieldNames, Class[] fieldTypes, byte[] fieldFlags,
    867               Class persistenceCapableSuperclass, PersistenceCapable pc) {
    868             this.fieldNames = fieldNames;
    869             this.fieldTypes = fieldTypes;
    870             this.fieldFlags = fieldFlags;
    871             this.persistenceCapableSuperclass = persistenceCapableSuperclass;
    872             this.pc = pc;
    873         }
    874
    875         /** This is an array of field names used
    876          * for the Model at runtime.  The field
    877          * is passed by the static class initialization.
    878          */
    879         String[] fieldNames;
    880
    881         /** Get the field names from the metadata.
    882          * @return the array of field names.
    883          */
    884         String[] getFieldNames() {
    885             return fieldNames;
    886         }
    887
    888         /** This is an array of field types used
    889          * for the Model at runtime.  The field
    890          * is passed by the static class initialization.
    891          */
    892         Class[] fieldTypes;
    893
    894         /** Get the field types from the metadata.
    895          * @return the array of field types.
    896          */
    897         Class[] getFieldTypes() {
    898             return fieldTypes;
    899         }
    900
    901         /** This is an array of field flags used
    902          * for the Model at runtime.  The field
    903          * is passed by the static class initialization.
    904          */
    905         byte[] fieldFlags;
    906
    907         /** Get the field types from the metadata.
    908          * @return the array of field types.
    909          */
    910         byte[] getFieldFlags() {
    911             return fieldFlags;
    912         }
    913
    914         /** This is the <code>Class</code> instance of the
    915          * <code>PersistenceCapable</code> superclass.
    916          */
    917         Class persistenceCapableSuperclass;
    918
    919         /** Return the <code>PersistenceCapable</code> superclass.
    920          * @return the <code>PersistenceCapable</code> superclass
    921          */
    922         Class getPersistenceCapableSuperclass() {
    923             return persistenceCapableSuperclass;
    924         }
    925         /** This is an instance of <code>PersistenceCapable</code>,
    926          * used at runtime to create new instances.
    927          */
    928         PersistenceCapable pc;
    929
    930         /** Get an instance of the <code>PersistenceCapable</code> class.
    931          * @return an instance of the <code>PersistenceCapable Class</code>.
    932          */
    933         PersistenceCapable getPC() {
    934             return pc;
    935         }
    936
    937         /** Return the string form of the metadata.
    938          * @return the string form
    939          */
    940         public String toString() {
    941             return "Meta-" + pc.getClass().getName(); //NOI18N
    942         }
    943     }
    944
    945     /**
    946      * Add a StateInterrogation to the list. Create a new list
    947      * in case there is an iterator open on the original list.
    948      * @param si the StateInterrogation to add
    949      */
    950     public synchronized void addStateInterrogation(StateInterrogation si) {
    951         List<StateInterrogation> newList =
    952                 new ArrayList<StateInterrogation>(stateInterrogations);
    953         newList.add(si);
    954         stateInterrogations = newList;
    955     }
    956
    957     /**
    958      * Remove a StateInterrogation from the list. Create a new list
    959      * in case there is an iterator open on the original list.
    960      * @param si the StateInterrogation to remove
    961      */
    962     public synchronized void removeStateInterrogation(StateInterrogation si) {
    963         List<StateInterrogation> newList =
    964                 new ArrayList<StateInterrogation>(stateInterrogations);
    965         newList.remove(si);
    966         stateInterrogations = newList;
    967     }
    968
    969     /**
    970      * Return an Iterator over all StateInterrogation instances.
    971      * Synchronize to avoid add/remove/iterate conflicts.
    972      * @return an Iterator over all StateInterrogation instances.
    973      */
    974     private synchronized Iterator getStateInterrogationIterator() {
    975         return stateInterrogations.iterator();
    976     }
    977
    978     /**
    979      * Mark a non-binary-compatible instance dirty. Delegate to all
    980      * registered StateInterrogation instances until one of them
    981      * handles the call.
    982      * @param pc the instance to mark dirty
    983      * @param fieldName the field to mark dirty
    984      */
    985     public void nonBinaryCompatibleMakeDirty(Object pc, String fieldName) {
    986         Iterator sit = getStateInterrogationIterator();
    987         while (sit.hasNext()) {
    988             StateInterrogation si = (StateInterrogation)sit.next();
    989             try {
    990                 if (si.makeDirty(pc, fieldName)) return;
    991             } catch (Throwable t) {
    992                 continue; // ignore exceptions from errant StateInterrogations
    993             }
    994         }
    995     }
    996
    997     /**
    998      * Determine the state of a non-binary-compatible instance.
    999      * Delegate to all registered StateInterrogation instances until
    1000      * one of them handles the call (returns a non-null Boolean
    1001      * with the answer).
    1002      * The caller provides the stateless "method object" that does
    1003      * the actual call to the StateInterrogation instance.
    1004      * @param pc the instance to be checked
    1005      * @param sibr the method object that delegates to the
    1006      * non-binary-compatible implementation
    1007      * @return Boolean.TRUE if the instance satisfies the state interrogation;
    1008      * Boolean.FALSE if the instance does not satisfy the interrogation;
    1009      * or null if the implementation does not manage the class of the instance
    1010      */
    1011     public boolean nonBinaryCompatibleIs(Object pc,
    1012             StateInterrogationBooleanReturn sibr) {
    1013         Iterator sit = getStateInterrogationIterator();
    1014         while (sit.hasNext()) {
    1015             StateInterrogation si = (StateInterrogation)sit.next();
    1016             Boolean result;
    1017             try {
    1018                 result = sibr.is(pc, si);
    1019             } catch (Throwable t) {
    1020                 continue; // ignore exceptions from errant StateInterrogations
    1021             }
    1022             if (result != null) return result.booleanValue();
    1023         }
    1024         return false;
    1025     }
    1026
    1027     /**
    1028      * Return an object associated with a non-binary-compatible instance.
    1029      * Delegate to all registered StateInterrogation instances until
    1030      * one of them handles the call (returns a non-null answer).
    1031      * The caller provides the stateless "method object" that does
    1032      * the actual call to the StateInterrogation instance.
    1033      * @param pc the instance whose associated object is needed
    1034      * @param sibr the method object that delegates to the
    1035      * non-binary-compatible implementation
    1036      * @return the associated object or null if the implementation does not
    1037      * manage the class of the instance
    1038      */
    1039     public Object nonBinaryCompatibleGet(Object pc,
    1040             StateInterrogationObjectReturn sibr) {
    1041         Iterator sit = getStateInterrogationIterator();
    1042         while (sit.hasNext()) {
    1043             StateInterrogation si = (StateInterrogation)sit.next();
    1044             Object result;
    1045             try {
    1046                 result = sibr.get(pc, si);
    1047             } catch (Throwable t) {
    1048                 continue; // ignore exceptions from errant StateInterrogations
    1049             }
    1050             if (result != null) return result;
    1051         }
    1052         return null;
    1053     }
    1054
    1055     /** This is an interface used to interrogate the state of an instance
    1056      * that does not implement PersistenceCapable. It is used for the
    1057      * methods that return a boolean value.
    1058      */
    1059     public static interface StateInterrogationBooleanReturn {
    1060         /**
    1061          * Interrogate the state of the instance
    1062          * @param pc the instance
    1063          * @param si the method object
    1064          * @return the state of the instance or null
    1065          */
    1066         public Boolean is(Object pc, StateInterrogation si);
    1067     }
    1068
    1069     /** This is an interface used to interrogate the state of an instance
    1070      * that does not implement PersistenceCapable. It is used for the
    1071      * methods that return an Object value.
    1072      */
    1073     public static interface StateInterrogationObjectReturn {
    1074         /**
    1075          * Return the associated instance.
    1076          * @param pc the instance
    1077          * @param si the method object
    1078          * @return the associated object or null
    1079          */
    1080         public Object get(Object pc, StateInterrogation si);
    1081     }
    1082 }