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