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