1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
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
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()));
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()));
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"));
362 Meta meta = new Meta (fieldNames, fieldTypes,
363 fieldFlags, persistenceCapableSuperclass, pc);
364 registeredClasses.put (pcClass, meta);
365
366
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
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
403
404
405
406
407
408
409 if ((pcClass != null) && (pcClass.getClassLoader() == cl)) {
410
411
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"));
430 SecurityManager sec = System.getSecurityManager();
431 if (sec != null) {
432
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
448
449
450
451
452 alreadyRegisteredClasses = new HashSet<Class> (registeredClasses.keySet());
453 }
454
455
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()));
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"));
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",
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
622 return;
623 }
624 synchronized(authorizedStateManagerClasses) {
625 if (authorizedStateManagerClasses.containsKey(smClass)) {
626 return;
627 }
628 }
629
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",
686 s), ex);
687 } catch (Exception ex) {
688 throw new JDOUserException(msg.msg(
689 "EXC_CurrencyStringConstructorException"),
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);
702 }
703 }
704 });
705 jdoImplHelper.registerStringConstructor(Date.class, new StringConstructor() {
706 public synchronized Object construct(String s) {
707 try {
708
709 return new Date(Long.parseLong(s));
710 } catch (NumberFormatException ex) {
711
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[]
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
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
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
793
794
795
796
797 throw new JDOUserException(
798 msg.msg("EXC_ObjectIdentityStringConstruction",
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");
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();
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;
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;
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;
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 }