001: /**********************************************************************
002: Copyright (c) 2002 Mike Martin (TJDO) and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: 2002 Kelly Grizzle (TJDO)
017: 2003 Erik Bengtson - refactored the persistent id generator System property
018: 2003 Erik Bengtson - added PMFConfiguration, PMFContext
019: 2003 Andy Jefferson - commented, and added ApplicationId to supported options
020: 2003 Andy Jefferson - introduction of localiser
021: 2004 Andy Jefferson - update to getProperties() method
022: 2004 Andy Jefferson - added LifecycleListener
023: 2004 Andy Jefferson - added Level 2 Cache
024: 2005 Marco Schulze - implemented copying the lifecycle listeners in j2ee environment
025: ...
026: **********************************************************************/package org.jpox.jdo;
027:
028: import java.util.ArrayList;
029: import java.util.Arrays;
030: import java.util.Collection;
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037: import java.util.Properties;
038: import java.util.Set;
039:
040: import javax.jdo.JDOFatalUserException;
041: import javax.jdo.JDOUserException;
042: import javax.jdo.datastore.DataStoreCache;
043: import javax.jdo.datastore.Sequence;
044: import javax.jdo.listener.InstanceLifecycleListener;
045: import javax.jdo.spi.JDOPermission;
046:
047: import org.jpox.ClassLoaderResolver;
048: import org.jpox.ObjectManagerFactoryImpl;
049: import org.jpox.exceptions.JPOXException;
050:
051: /**
052: * Factory used to obtain {@link javax.jdo.PersistenceManager} instances.
053: * The factory is configurable up to a point when it is frozen. Thereafter nothing can be changed.
054: *
055: * @version $Revision: 1.1 $
056: */
057: public abstract class AbstractPersistenceManagerFactory extends
058: ObjectManagerFactoryImpl {
059: private static final String VERSION_NUMBER_PROPERTY = "VersionNumber";
060: private static final String VENDOR_NAME_PROPERTY = "VendorName";
061:
062: /** The cache of PM's in use */
063: private Set pmCache = new HashSet();
064:
065: /** Lifecycle Listeners */
066: protected List lifecycleListeners;
067:
068: /** Map of user-defined sequences keyed by the factory class name. */
069: private Map sequenceByFactoryClass;
070:
071: /** Level 2 Cache. */
072: private DataStoreCache datastoreCache = null;
073:
074: /**
075: * Constructor.
076: */
077: public AbstractPersistenceManagerFactory() {
078: super ();
079:
080: try {
081: Class cls = javax.jdo.PersistenceManager.class;
082: cls.getMethod("detachCopy", new Class[] { Object.class });
083: } catch (SecurityException e) {
084: throw new JDOFatalUserException(LOCALISER.msg("012003"));
085: } catch (NoSuchMethodException e) {
086: throw new JDOFatalUserException(LOCALISER.msg("012003"));
087: }
088: }
089:
090: /**
091: * Freezes the current configuration.
092: * @throws JPOXException if the configuration was invalid or inconsistent in some way
093: */
094: protected void freezeConfiguration() {
095: if (configurable) {
096: try {
097: // Log the PMF configuration
098: logConfiguration();
099:
100: // Set user classloader
101: ClassLoaderResolver clr = getOMFContext()
102: .getClassLoaderResolver(null);
103: clr.registerUserClassLoader(getPrimaryClassLoader());
104:
105: // Set up the StoreManager
106: initialiseStoreManager(clr);
107:
108: // Set up the Level 2 Cache
109: initialiseLevel2Cache();
110:
111: if (cache != null) {
112: datastoreCache = new JDODataStoreCache(cache);
113: }
114:
115: configurable = false;
116: } catch (JPOXException jpe) {
117: throw JPOXJDOHelper
118: .getJDOExceptionForJPOXException(jpe);
119: }
120: }
121: }
122:
123: /**
124: * Return non-configurable properties of this PersistenceManagerFactory.
125: * Properties with keys VendorName and VersionNumber are required. Other keys are optional.
126: * @return the non-configurable properties of this PersistenceManagerFactory.
127: */
128: public Properties getProperties() {
129: Properties props = new Properties();
130:
131: props.setProperty(VENDOR_NAME_PROPERTY, getVendorName());
132: props.setProperty(VERSION_NUMBER_PROPERTY, getVersionNumber());
133:
134: return props;
135: }
136:
137: /**
138: * The application can determine from the results of this method which
139: * optional features, and which query languages are supported by the JDO
140: * implementation. Se esection 11.6 of the JDO 2 specification.
141: * @return A Collection of String representing the supported options.
142: */
143: public Collection supportedOptions() {
144: return Collections
145: .unmodifiableList(Arrays.asList(OPTION_ARRAY));
146: }
147:
148: /**
149: * The JDO spec optional features that JPOX supports.
150: * See JDO 2.0 spec section 11.6 for the full list of possibilities.
151: **/
152: private static final String[] OPTION_ARRAY = {
153: "javax.jdo.option.TransientTransactional",
154: "javax.jdo.option.NontransactionalWrite",
155: "javax.jdo.option.NontransactionalRead",
156: "javax.jdo.option.RetainValues",
157: "javax.jdo.option.Optimistic",
158: "javax.jdo.option.ApplicationIdentity",
159: "javax.jdo.option.DatastoreIdentity",
160: "javax.jdo.option.NonDurableIdentity",
161: "javax.jdo.option.ArrayList",
162: "javax.jdo.option.LinkedList",
163: "javax.jdo.option.TreeSet",
164: "javax.jdo.option.TreeMap",
165: "javax.jdo.option.Vector",
166: "javax.jdo.option.List",
167: "javax.jdo.option.Stack", // Not a listed JDO2 feature
168: "javax.jdo.option.Map", // Not a listed JDO2 feature
169: "javax.jdo.option.HashMap", // Not a listed JDO2 feature
170: "javax.jdo.option.Hashtable", // Not a listed JDO2 feature
171: "javax.jdo.option.SortedSet", // Not a listed JDO2 feature
172: "javax.jdo.option.SortedMap", // Not a listed JDO2 feature
173: "javax.jdo.option.Array",
174: "javax.jdo.option.NullCollection",
175: // "javax.jdo.option.ChangeApplicationIdentity",
176: "javax.jdo.option.BinaryCompatibility",
177: "javax.jdo.option.GetDataStoreConnection",
178: "javax.jdo.option.GetJDBCConnection",
179: "javax.jdo.query.SQL",
180: "javax.jdo.option.UnconstrainedQueryVariables",
181: "javax.jdo.option.version.DateTime",
182: "javax.jdo.option.PreDirtyEvent",
183: "javax.jdo.option.mapping.HeterogeneousObjectType",
184: "javax.jdo.option.mapping.HeterogeneousInterfaceType",
185: "javax.jdo.option.mapping.JoinedTablePerClass",
186: "javax.jdo.option.mapping.JoinedTablePerConcreteClass",
187: "javax.jdo.option.mapping.NonJoinedTablePerConcreteClass",
188: // "javax.jdo.option.mapping.RelationSubclassTable", // Not yet supported for multiple subclasses
189: "javax.jdo.query.JDOQL", // Not a listed optional feature
190: "javax.jdo.query.JPOXSQL" };
191:
192: /**
193: * Return the Set of PersistenceManagers actually in cache
194: * @return Set this contains instances of PersistenceManagers in the cache
195: */
196: protected Set getPmCache() {
197: return pmCache;
198: }
199:
200: /**
201: * Remove a PersistenceManager from the cache
202: * Only the PersistenceManager is allowed to call this method
203: * @param om the PersistenceManager to be removed from cache
204: */
205: public void releasePersistenceManager(AbstractPersistenceManager om) {
206: getPmCache().remove(om);
207: }
208:
209: /**
210: * Asserts that the PMF is open.
211: * @throws JDOUserException if it is already closed
212: */
213: protected void assertIsOpen() {
214: try {
215: super .assertIsOpen();
216: } catch (JPOXException jpe) {
217: // Comply with Section 11.4 of the JDO2 spec (throw JDOUserException if already closed)
218: throw JPOXJDOHelper.getJDOExceptionForJPOXException(jpe);
219: }
220: }
221:
222: /**
223: * Close this PersistenceManagerFactory. Check for JDOPermission("closePersistenceManagerFactory")
224: * and if not authorized, throw SecurityException.
225: * <P>If the authorization check succeeds, check to see that all PersistenceManager instances obtained
226: * from this PersistenceManagerFactory have no active transactions. If any PersistenceManager instances
227: * have an active transaction, throw a JDOUserException, with one nested JDOUserException for each
228: * PersistenceManager with an active Transaction.
229: * <P>If there are no active transactions, then close all PersistenceManager instances obtained from
230: * this PersistenceManagerFactory, mark this PersistenceManagerFactory as closed, disallow
231: * getPersistenceManager methods, and allow all other get methods. If a set method or getPersistenceManager
232: * method is called after close, then JDOUserException is thrown.
233: * @see javax.jdo.PersistenceManagerFactory#close()
234: */
235: public synchronized void close() {
236: assertIsOpen();
237:
238: SecurityManager secmgr = System.getSecurityManager();
239: if (secmgr != null) {
240: // checkPermission will throw SecurityException if not authorized
241: secmgr
242: .checkPermission(JDOPermission.CLOSE_PERSISTENCE_MANAGER_FACTORY);
243: }
244:
245: // iterate though the list of pms to release resources
246: Iterator pms = new HashSet(getPmCache()).iterator();
247: Set exceptions = new HashSet();
248: while (pms.hasNext()) {
249: try {
250: ((AbstractPersistenceManager) pms.next()).close();
251: } catch (JDOUserException ex) {
252: exceptions.add(ex);
253: }
254: }
255: if (!exceptions.isEmpty()) {
256: throw new JDOUserException(LOCALISER.msg("012002"),
257: (Throwable[]) exceptions
258: .toArray(new Throwable[exceptions.size()]));
259: }
260:
261: // Let superclass close its resources
262: super .close();
263: }
264:
265: /**
266: * Accessor for the DataStore (level 2) Cache
267: * @return The datastore cache
268: * @since 1.1
269: */
270: public DataStoreCache getDataStoreCache() {
271: freezeConfiguration();
272:
273: return datastoreCache;
274: }
275:
276: // -------------------------------- Lifecycle Listeners -------------------------------
277:
278: /**
279: * @return Returns either <tt>null</tt> or a <tt>List</tt> with instances of
280: * <tt>LifecycleListenerSpecification</tt>.
281: */
282: public List getLifecycleListenerSpecifications() {
283: return lifecycleListeners;
284: }
285:
286: /**
287: * Method to add lifecycle listeners for particular classes.
288: * Adds the listener to all PMs already created.
289: * @param listener The listener
290: * @param classes The classes that the listener relates to
291: * @since 1.1
292: */
293: public void addInstanceLifecycleListener(
294: InstanceLifecycleListener listener, Class[] classes) {
295: if (listener == null) {
296: return;
297: }
298:
299: synchronized (pmCache) {
300: // Add to any PMs - make sure that the PM Cache isnt changed while doing so
301: Iterator pms = getPmCache().iterator();
302: while (pms.hasNext()) {
303: AbstractPersistenceManager pm = (AbstractPersistenceManager) pms
304: .next();
305: pm.addInstanceLifecycleListener(listener, classes);
306: }
307: }
308:
309: // Register the lifecycle listener
310: if (lifecycleListeners == null) {
311: lifecycleListeners = new ArrayList(5);
312: }
313: lifecycleListeners.add(new LifecycleListenerForClass(listener,
314: classes));
315: }
316:
317: /**
318: * Method to remove a lifecycle listener. Removes the listener from all PM's as well.
319: * @param listener The Listener
320: * @since 1.1
321: */
322: public void removeInstanceLifecycleListener(
323: InstanceLifecycleListener listener) {
324: if (listener == null || lifecycleListeners == null) {
325: return;
326: }
327:
328: synchronized (pmCache) {
329: // Remove from any PMs - make sure that the PM Cache isnt changed while doing so
330: Iterator pms = getPmCache().iterator();
331: while (pms.hasNext()) {
332: AbstractPersistenceManager pm = (AbstractPersistenceManager) pms
333: .next();
334: pm.removeInstanceLifecycleListener(listener);
335: }
336: }
337:
338: // Remove from the PMF
339: Iterator iter = lifecycleListeners.iterator();
340: while (iter.hasNext()) {
341: LifecycleListenerForClass classListener = (LifecycleListenerForClass) iter
342: .next();
343: if (classListener.getListener() == listener) {
344: iter.remove();
345: }
346: }
347: }
348:
349: // --------------------------- Sequences ----------------------------------
350:
351: /**
352: * Method to register a sequence for a particular factory class.
353: * @param factoryClassName Name of the factory class
354: * @param sequence The sequence
355: */
356: public void addSequenceForFactoryClass(String factoryClassName,
357: Sequence sequence) {
358: if (sequenceByFactoryClass == null) {
359: sequenceByFactoryClass = new HashMap();
360: }
361:
362: sequenceByFactoryClass.put(factoryClassName, sequence);
363: }
364:
365: /**
366: * Accessor for the sequence for a factory class.
367: * @param factoryClassName The name of the factory class
368: * @return The sequence
369: */
370: public Sequence getSequenceForFactoryClass(String factoryClassName) {
371: if (sequenceByFactoryClass == null) {
372: return null;
373: }
374:
375: return (Sequence) sequenceByFactoryClass.get(factoryClassName);
376: }
377: }
|