View Javadoc

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