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         if (isClassLoadable("java.util.Currency")) {
673             jdoImplHelper.registerStringConstructor(
674                     Currency.class, new StringConstructor() {
675                 public Object construct(String s) {
676                     try {
677                         return Currency.getInstance(s);
678                     } catch (IllegalArgumentException ex) {
679                         throw new javax.jdo.JDOUserException(msg.msg(
680                             "EXC_CurrencyStringConstructorIllegalArgument", //NOI18N
681                             s), ex); 
682                     } catch (Exception ex) {
683                         throw new JDOUserException(msg.msg(
684                             "EXC_CurrencyStringConstructorException"), //NOI18N
685                             ex); 
686                     }
687                 }
688             });
689         }
690         jdoImplHelper.registerStringConstructor(Locale.class, new StringConstructor() {
691             public Object construct(String s) {
692                 try {
693                     return getLocale(s);
694                 } catch (Exception ex) {
695                     throw new JDOUserException(msg.msg(
696                         "EXC_LocaleStringConstructorException"), ex); //NOI18N
697                 }
698             }
699         });
700         jdoImplHelper.registerStringConstructor(Date.class, new StringConstructor() {
701             public synchronized Object construct(String s) {
702                 try {
703                     // first, try the String as a Long
704                     return new Date(Long.parseLong(s));
705                 } catch (NumberFormatException ex) {
706                     // not a Long; try the formatted date
707                     ParsePosition pp = new ParsePosition(0);
708                     Date result = dateFormat.parse(s, pp);
709                     if (result == null) {
710                         throw new JDOUserException (msg.msg(
711                             "EXC_DateStringConstructor", new Object[] //NOI18N
712                             {s, new Integer(pp.getErrorIndex()), 
713                              dateFormatPattern}));
714                     }
715                     return result;
716                 }
717             }
718         });
719     }
720     
721     /***
722      * Parse the String to a Locale.
723      * @param s the name of the Locale
724      * @return the Locale corresponding to the name
725      */
726     private static Locale getLocale(String s) {
727         String lang = s;
728         int firstUnderbar = s.indexOf('_');
729         if (firstUnderbar == -1) {
730             // nothing but language
731             return new Locale(lang);
732         }
733         lang = s.substring(0, firstUnderbar);
734         String country;
735         int secondUnderbar = s.indexOf('_', firstUnderbar + 1);
736         if (secondUnderbar == -1) {
737             // nothing but language, country
738             country = s.substring(firstUnderbar + 1);
739             return new Locale(lang, country);
740         }
741         country = s.substring(firstUnderbar + 1, secondUnderbar);
742         String variant = s.substring(secondUnderbar + 1);
743         return new Locale(lang, country, variant);
744     }
745     /***
746      * Determine if a class is loadable in the current environment.
747      * @param className the fully-qualified name of the class
748      * @return true if the class can be loaded; false otherwise
749      */
750     private static boolean isClassLoadable(String className) {
751         try {
752             Class.forName(className);
753             return true;
754         } catch (ClassNotFoundException ex) {
755             return false;
756         }
757     }
758     
759     /***
760      * Construct an instance of the parameter class, using the keyString
761      * as an argument to the constructor. If the class has a StringConstructor
762      * instance registered, use it. If not, try to find a constructor for
763      * the class with a single String argument. Otherwise, throw a
764      * JDOUserException.
765      * @param className the name of the class
766      * @param keyString the String parameter for the constructor
767      * @return the result of construction
768      */
769     public static Object construct(String className, String keyString) {
770         StringConstructor stringConstructor;
771         try {
772             Class keyClass = Class.forName(className);
773             synchronized(stringConstructorMap) {
774                 stringConstructor = 
775                         (StringConstructor) stringConstructorMap.get(keyClass);
776             }
777             if (stringConstructor != null) {
778                 return stringConstructor.construct(keyString);
779             } else {
780                 Constructor keyConstructor = 
781                     keyClass.getConstructor(new Class[]{String.class});
782                 return keyConstructor.newInstance(new Object[]{keyString});
783             }
784         } catch (JDOException ex) {
785             throw ex;
786         } catch (Exception ex) {
787              /* ClassNotFoundException,
788                 NoSuchMethodException,
789                 InstantiationException,
790                 IllegalAccessException,
791                 InvocationTargetException */
792             throw new JDOUserException(
793                 msg.msg("EXC_ObjectIdentityStringConstruction",  //NOI18N
794                 new Object[] {ex.toString(), className, keyString}), ex);
795         }
796     }
797 
798     /***
799      * Get the DateFormat instance for the default locale from the VM.
800      * This requires the following privileges for JDOImplHelper in the
801      * security permissions file:
802      * permission java.util.PropertyPermission "user.country", "read";
803      * permission java.util.PropertyPermission "user.timezone", "read,write";
804      * permission java.util.PropertyPermission "java.home", "read";
805      * If these permissions are not present, or there is some other
806      * problem getting the default date format, a simple formatter is returned.
807      * @since 2.1
808      * @return the default date-time formatter
809      */
810     static DateFormat getDateTimeInstance() {
811         DateFormat result = null;
812         try {
813         result = (DateFormat) AccessController.doPrivileged (
814             new PrivilegedAction () {
815                 public Object run () {
816                     return DateFormat.getDateTimeInstance();
817                 }
818             }
819             );
820         } catch (Exception ex) {
821             result = DateFormat.getInstance();
822         }
823         return result;
824     }
825 
826     /***
827      * Register a DateFormat instance for use with constructing Date 
828      * instances. The default is the default DateFormat instance.
829      * If the new instance implements SimpleDateFormat, get its pattern
830      * for error messages.
831      * @since 2.0
832      * @param df the DateFormat instance to use
833      */
834     public synchronized void registerDateFormat(DateFormat df) {
835         dateFormat = df;
836         if (df instanceof SimpleDateFormat) {
837             dateFormatPattern = ((SimpleDateFormat)df).toPattern();
838         } else {
839             dateFormatPattern = msg.msg("MSG_unknown"); //NOI18N
840         }
841     }
842 
843     /*** This is a helper class to manage metadata per persistence-capable
844      * class.  The information is used at runtime to provide field names and
845      * field types to the JDO Model.
846      *
847      * This is the value of the <code>HashMap</code> which
848      * relates the <code>PersistenceCapable Class</code>
849      * as a key to the metadata.
850      */    
851     static class Meta {
852         
853         /*** Construct an instance of <code>Meta</code>.
854          * @param fieldNames An array of <code>String</code>
855          * @param fieldTypes An array of <code>Class</code>
856          * @param fieldFlags an array of <code>int</code>
857          * @param persistenceCapableSuperclass the most immediate 
858          * <code>PersistenceCapable</code> superclass
859          * @param pc An instance of the <code>PersistenceCapable</code> class
860          */        
861         Meta (String[] fieldNames, Class[] fieldTypes, byte[] fieldFlags,
862               Class persistenceCapableSuperclass, PersistenceCapable pc) {
863             this.fieldNames = fieldNames;
864             this.fieldTypes = fieldTypes;
865             this.fieldFlags = fieldFlags;
866             this.persistenceCapableSuperclass = persistenceCapableSuperclass;
867             this.pc = pc;
868         }
869     
870         /*** This is an array of field names used
871          * for the Model at runtime.  The field
872          * is passed by the static class initialization.
873          */
874         String[] fieldNames;
875     
876         /*** Get the field names from the metadata.
877          * @return the array of field names.
878          */
879         String[] getFieldNames() {
880             return fieldNames;
881         }
882     
883         /*** This is an array of field types used
884          * for the Model at runtime.  The field
885          * is passed by the static class initialization.
886          */
887         Class[] fieldTypes;
888     
889         /*** Get the field types from the metadata.
890          * @return the array of field types.
891          */
892         Class[] getFieldTypes() {
893             return fieldTypes;
894         }
895     
896         /*** This is an array of field flags used
897          * for the Model at runtime.  The field
898          * is passed by the static class initialization.
899          */
900         byte[] fieldFlags;
901     
902         /*** Get the field types from the metadata.
903          * @return the array of field types.
904          */
905         byte[] getFieldFlags() {
906             return fieldFlags;
907         }
908 
909         /*** This is the <code>Class</code> instance of the 
910          * <code>PersistenceCapable</code> superclass.
911          */
912         Class persistenceCapableSuperclass;
913     
914         /*** Return the <code>PersistenceCapable</code> superclass.
915          * @return the <code>PersistenceCapable</code> superclass
916          */
917         Class getPersistenceCapableSuperclass() {
918             return persistenceCapableSuperclass;
919         }
920         /*** This is an instance of <code>PersistenceCapable</code>,
921          * used at runtime to create new instances.
922          */
923         PersistenceCapable pc;
924     
925         /*** Get an instance of the <code>PersistenceCapable</code> class.
926          * @return an instance of the <code>PersistenceCapable Class</code>.
927          */
928         PersistenceCapable getPC() {
929             return pc;
930         }
931     
932         /*** Return the string form of the metadata.
933          * @return the string form
934          */
935         public String toString() {
936             return "Meta-" + pc.getClass().getName(); //NOI18N
937         }
938     }
939     
940     /***
941      * Add a StateInterrogation to the list. Create a new list
942      * in case there is an iterator open on the original list.
943      * @param si the StateInterrogation to add
944      */
945     public synchronized void addStateInterrogation(StateInterrogation si) {
946         List newList = new ArrayList(stateInterrogations);
947         newList.add(si);
948         stateInterrogations = newList;
949     }
950     
951     /***
952      * Remove a StateInterrogation from the list. Create a new list
953      * in case there is an iterator open on the original list.
954      * @param si the StateInterrogation to remove
955      */
956     public synchronized void removeStateInterrogation(StateInterrogation si) {
957         List newList = new ArrayList(stateInterrogations);
958         newList.remove(si);
959         stateInterrogations = newList;
960     }
961     
962     /***
963      * Return an Iterator over all StateInterrogation instances.
964      * Synchronize to avoid add/remove/iterate conflicts.
965      * @return an Iterator over all StateInterrogation instances.
966      */
967     private synchronized Iterator getStateInterrogationIterator() {
968         return stateInterrogations.iterator();
969     }
970     
971     /***
972      * Mark a non-binary-compatible instance dirty. Delegate to all
973      * registered StateInterrogation instances until one of them
974      * handles the call.
975      * @param pc the instance to mark dirty
976      * @param fieldName the field to mark dirty
977      */
978     public void nonBinaryCompatibleMakeDirty(Object pc, String fieldName) {
979         Iterator sit = getStateInterrogationIterator();
980         while (sit.hasNext()) {
981             StateInterrogation si = (StateInterrogation)sit.next();
982             try {
983                 if (si.makeDirty(pc, fieldName)) return;
984             } catch (Throwable t) {
985                 continue; // ignore exceptions from errant StateInterrogations
986             }
987         }
988     }
989     
990     /***
991      * Determine the state of a non-binary-compatible instance.
992      * Delegate to all registered StateInterrogation instances until
993      * one of them handles the call (returns a non-null Boolean 
994      * with the answer).
995      * The caller provides the stateless "method object" that does 
996      * the actual call to the StateInterrogation instance.
997      * @param pc the instance to be checked
998      * @param sibr the method object that delegates to the 
999      * non-binary-compatible implementation
1000      * @return Boolean.TRUE if the instance satisfies the state interrogation;
1001      * Boolean.FALSE if the instance does not satisfy the interrogation;
1002      * or null if the implementation does not manage the class of the instance
1003      */
1004     public boolean nonBinaryCompatibleIs(Object pc, 
1005             StateInterrogationBooleanReturn sibr) {
1006         Iterator sit = getStateInterrogationIterator();
1007         while (sit.hasNext()) {
1008             StateInterrogation si = (StateInterrogation)sit.next();
1009             Boolean result;
1010             try {
1011                 result = sibr.is(pc, si);
1012             } catch (Throwable t) {
1013                 continue; // ignore exceptions from errant StateInterrogations
1014             }
1015             if (result != null) return result.booleanValue();
1016         }
1017         return false;
1018     }
1019     
1020     /***
1021      * Return an object associated with a non-binary-compatible instance.
1022      * Delegate to all registered StateInterrogation instances until
1023      * one of them handles the call (returns a non-null answer).
1024      * The caller provides the stateless "method object" that does 
1025      * the actual call to the StateInterrogation instance.
1026      * @param pc the instance whose associated object is needed
1027      * @param sibr the method object that delegates to the 
1028      * non-binary-compatible implementation
1029      * @return the associated object or null if the implementation does not
1030      * manage the class of the instance
1031      */
1032     public Object nonBinaryCompatibleGet(Object pc, 
1033             StateInterrogationObjectReturn sibr) {
1034         Iterator sit = getStateInterrogationIterator();
1035         while (sit.hasNext()) {
1036             StateInterrogation si = (StateInterrogation)sit.next();
1037             Object result;
1038             try {
1039                 result = sibr.get(pc, si);
1040             } catch (Throwable t) {
1041                 continue; // ignore exceptions from errant StateInterrogations
1042             }
1043             if (result != null) return result;
1044         }
1045         return null;
1046     }
1047     
1048     /*** This is an interface used to interrogate the state of an instance
1049      * that does not implement PersistenceCapable. It is used for the
1050      * methods that return a boolean value.
1051      */
1052     public static interface StateInterrogationBooleanReturn {
1053         /***
1054          * Interrogate the state of the instance 
1055          * @param pc the instance
1056          * @param si the method object
1057          * @return the state of the instance or null
1058          */
1059         public Boolean is(Object pc, StateInterrogation si);
1060     }
1061     
1062     /*** This is an interface used to interrogate the state of an instance
1063      * that does not implement PersistenceCapable. It is used for the
1064      * methods that return an Object value.
1065      */
1066     public static interface StateInterrogationObjectReturn {
1067         /***
1068          * Return the associated instance.
1069          * @param pc the instance
1070          * @param si the method object
1071          * @return the associated object or null
1072          */
1073         public Object get(Object pc, StateInterrogation si);
1074     }
1075 }