001: /*--
002:
003: Copyright (C) 2002-2005 Adrian Price.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The names "OBE" and "Open Business Engine" must not be used to
019: endorse or promote products derived from this software without prior
020: written permission. For written permission, please contact
021: adrianprice@sourceforge.net.
022:
023: 4. Products derived from this software may not be called "OBE" or
024: "Open Business Engine", nor may "OBE" or "Open Business Engine"
025: appear in their name, without prior written permission from
026: Adrian Price (adrianprice@users.sourceforge.net).
027:
028: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
029: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
030: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
031: DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
032: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
033: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
034: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
035: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
036: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
037: IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
038: POSSIBILITY OF SUCH DAMAGE.
039:
040: For more information on OBE, please see
041: <http://obe.sourceforge.net/>.
042:
043: */
044:
045: package org.obe.engine.repository;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049: import org.exolab.castor.mapping.Mapping;
050: import org.exolab.castor.mapping.MappingException;
051: import org.exolab.castor.xml.*;
052: import org.obe.OBERuntimeException;
053: import org.obe.client.api.repository.AbstractMetaData;
054: import org.obe.client.api.repository.ObjectAlreadyExistsException;
055: import org.obe.client.api.repository.ObjectNotFoundException;
056: import org.obe.client.api.repository.RepositoryException;
057: import org.obe.spi.WorkflowService;
058: import org.obe.spi.service.ServerConfig;
059: import org.obe.spi.service.ServiceManager;
060: import org.obe.util.CommonConfig;
061: import org.xml.sax.EntityResolver;
062: import org.xml.sax.InputSource;
063:
064: import java.io.FileWriter;
065: import java.io.IOException;
066: import java.io.Writer;
067: import java.lang.reflect.Array;
068: import java.util.*;
069:
070: /**
071: * An abstract base class for basic repository implementations. It is
072: * essentially an abstract object 'factory factory'.
073: *
074: * @author Adrian Price
075: */
076: public abstract class AbstractRepository implements WorkflowService {
077: protected static final String SERVICE = "Service ";
078: protected static final String NOT_FOUND_MSG = "Repository does not contain key: ";
079: private static Mapping _mapping;
080: protected final ServiceManager _svcMgr;
081: private final Class _metaDataClass;
082: private final Map _objectTypes = new HashMap();
083: private final Map _entries = new HashMap();
084: private AbstractMetaData[] _metaData;
085: private boolean _initialized;
086: private boolean _modified;
087:
088: private class RepositoryUnmarshalListener implements
089: UnmarshalListener {
090: private final boolean _debug;
091: private final Map _idToObject;
092:
093: RepositoryUnmarshalListener(boolean debug, Map idToObject) {
094: _debug = debug;
095: _idToObject = idToObject;
096: }
097:
098: public void initialized(Object object) {
099: if (_debug)
100: getLogger().debug("initialized(\"" + object + "\")");
101: }
102:
103: public void attributesProcessed(Object object) {
104: if (_debug) {
105: getLogger().debug(
106: "attributesProcessed(\"" + object + "\")");
107: }
108: }
109:
110: public void fieldAdded(String fieldName, Object parent,
111: Object child) {
112:
113: if (_debug) {
114: getLogger().debug(
115: "fieldAdded(\"" + fieldName + "\", \"" + parent
116: + "\", \"" + child + "\")");
117: }
118: }
119:
120: public void unmarshalled(Object object) {
121: if (_debug)
122: getLogger().debug("unmarshalled(\"" + object + "\")");
123:
124: // We do this to compensate for Castor's mis-handling of
125: // object identity. It manages to keep track of
126: // AbstractMetaData subclasses because it always knows
127: // when to expect one, but fields of type Object get
128: // ignored even if their runtime class declares or
129: // inherits an identity field.
130: if (object instanceof RepositoryEntry) {
131: RepositoryEntry entry = (RepositoryEntry) object;
132: if (entry.getMetaData() == null) {
133: Object instance = entry.getInstance();
134: if (instance != null)
135: _idToObject.put(entry.getKey(), instance);
136: }
137: }
138: }
139: }
140:
141: private static synchronized Mapping getMapping() {
142: if (_mapping == null) {
143: _mapping = new Mapping(AbstractRepository.class
144: .getClassLoader());
145: try {
146: InputSource src = CommonConfig
147: .openInputSource("castor-map.xml");
148: if (src != null) {
149: _mapping.loadMapping(src);
150: src.getByteStream().close();
151: }
152: } catch (IOException e) {
153: throw new OBERuntimeException(e);
154: } catch (MappingException e) {
155: throw new OBERuntimeException(e);
156: }
157: }
158: return _mapping;
159: }
160:
161: protected static class Entry {
162: private AbstractMetaData _metadata;
163: private Object _instance;
164:
165: Entry(AbstractMetaData metadata, Object instance) {
166: _metadata = metadata;
167: _instance = instance;
168: }
169:
170: public AbstractMetaData getMetaData() {
171: return _metadata;
172: }
173:
174: public Object getInstance(EntityResolver entityResolver)
175: throws RepositoryException {
176:
177: return getInstance(entityResolver, null);
178: }
179:
180: public Object getInstance(EntityResolver entityResolver,
181: Object state) throws RepositoryException {
182:
183: Object instance;
184: if (_instance != null) {
185: // Singletons must be threadsafe by definition.
186: instance = _instance;
187: } else {
188: if (_metadata.isThreadsafe()) {
189: // If the instance class is threadsafe, create one lazily.
190: if (_instance == null)
191: _instance = _metadata
192: .createInstance(entityResolver);
193: instance = _instance;
194: } else {
195: // TODO: Think about reusability, pooling, TLS.
196: // If not threadsafe, create a new instance each time.
197: instance = _metadata.createInstance(entityResolver,
198: state);
199: }
200: }
201: return instance;
202: }
203: }
204:
205: protected static Log getLog(Class clazz) {
206: return LogFactory.getLog(clazz);
207: }
208:
209: protected AbstractRepository(ServiceManager svcMgr,
210: Class metaDataClass) {
211: _svcMgr = svcMgr;
212: _metaDataClass = metaDataClass;
213: }
214:
215: protected String getConfigurationFileName() {
216: String classname = getClass().getName();
217: classname = classname.substring(classname.lastIndexOf('.') + 1);
218: return classname + ".xml";
219: }
220:
221: public synchronized void init() throws IOException,
222: RepositoryException {
223: if (_initialized) {
224: throw new IllegalStateException(SERVICE + getServiceName()
225: + " already initialized");
226: }
227: _initialized = true;
228:
229: load();
230: }
231:
232: public synchronized void exit() {
233: if (!_initialized) {
234: throw new IllegalStateException(SERVICE + getServiceName()
235: + " not initialized");
236: }
237: _initialized = false;
238:
239: // if (_modified)
240: // store();
241: _entries.clear();
242: _mapping = null;
243: _modified = false;
244: }
245:
246: protected synchronized void load() throws RepositoryException,
247: IOException {
248: clear();
249:
250: InputSource src = null;
251: try {
252: // Attempt to locate the backing file.
253: src = CommonConfig
254: .openInputSource(getConfigurationFileName());
255: if (src != null) {
256: Unmarshaller unmarshaller = new Unmarshaller(
257: RepositoryEntries.class, getClass()
258: .getClassLoader());
259: final Map idToObject = new HashMap();
260: unmarshaller
261: .setUnmarshalListener(new RepositoryUnmarshalListener(
262: getLogger().isDebugEnabled()
263: && ServerConfig.isVerbose(),
264: idToObject));
265: unmarshaller.setIDResolver(new IDResolver() {
266: public Object resolve(String idref) {
267: return idToObject.get(idref);
268: }
269: });
270: unmarshaller.setValidation(false);
271: unmarshaller.setMapping(getMapping());
272: RepositoryEntries wrapper = (RepositoryEntries) unmarshaller
273: .unmarshal(src);
274: AbstractMetaData[] types = wrapper.type;
275: RepositoryEntry[] entries = wrapper.entry;
276: if (getLogger().isDebugEnabled()) {
277: getLogger()
278: .debug(
279: "Found "
280: + (types == null ? 0
281: : types.length)
282: + " types and "
283: + (entries == null ? 0
284: : entries.length)
285: + " entries");
286: }
287: if (types != null) {
288: for (int i = 0; i < types.length; i++)
289: registerObjectType(types[i]);
290: }
291: if (entries != null) {
292: for (int i = 0; i < entries.length; i++) {
293: RepositoryEntry entry = entries[i];
294: try {
295: createEntry(entry.getKey(), entry
296: .getMetaData(), entry.getInstance());
297: } catch (Exception e) {
298: getLogger().error(
299: "Unable to create repository entry for "
300: + entry.getKey(), e);
301: }
302: }
303: }
304: }
305: } catch (Exception e) {
306: getLogger().error("Error loading " + getServiceName(), e);
307: } finally {
308: if (src != null) {
309: try {
310: src.getByteStream().close();
311: } catch (IOException e) {
312: // We don't care about stream close exceptions.
313: }
314: }
315: _modified = false;
316: }
317: }
318:
319: public synchronized void store() {
320: if (!_modified)
321: return;
322:
323: boolean allowInheritance = AbstractMetaData.allowInheritance;
324: Writer writer = null;
325: try {
326: // Must disable inheritance while storing repository contents
327: // (otherwise inherited values would get persisted for all
328: // inheritors).
329: AbstractMetaData.allowInheritance = false;
330:
331: writer = new FileWriter(getConfigurationFileName());
332: Collection coll = _objectTypes.values();
333: AbstractMetaData[] types = (AbstractMetaData[]) coll
334: .toArray(new AbstractMetaData[coll.size()]);
335: Set mapEntries = _entries.entrySet();
336: RepositoryEntry[] entries = new RepositoryEntry[mapEntries
337: .size()];
338: int j = 0;
339: Iterator iter = mapEntries.iterator();
340: while (iter.hasNext()) {
341: Map.Entry mapEntry = (Map.Entry) iter.next();
342: Entry entry = (Entry) mapEntry.getValue();
343: entries[j] = new RepositoryEntry((String) mapEntry
344: .getKey(), entry._metadata,
345: entry._metadata == null ? entry._instance
346: : null);
347: j++;
348: }
349: Marshaller marshaller = new Marshaller(writer);
350: marshaller.setMapping(getMapping());
351: marshaller.setRootElement("repository");
352: if (getLogger().isDebugEnabled()
353: && ServerConfig.isVerbose()) {
354: marshaller.setMarshalListener(new MarshalListener() {
355: public boolean preMarshal(Object object) {
356: getLogger().debug(
357: "preMarshal(\"" + object + "\")");
358: return true;
359: }
360:
361: public void postMarshal(Object object) {
362: getLogger().debug(
363: "postMarshal(\"" + object + "\")");
364: }
365: });
366: }
367: marshaller.marshal(new RepositoryEntries(types, entries));
368:
369: writer.flush();
370:
371: _modified = false;
372: } catch (Exception e) {
373: getLogger().error("Error saving repository contents", e);
374: } finally {
375: AbstractMetaData.allowInheritance = allowInheritance;
376: if (writer != null)
377: try {
378: writer.close();
379: } catch (IOException e) {
380: // We don't care about exceptions on close.
381: }
382: }
383: }
384:
385: protected synchronized void clear() {
386: _entries.clear();
387: _modified = true;
388: }
389:
390: public synchronized boolean isInitialized() {
391: return _initialized;
392: }
393:
394: protected void registerObjectType(AbstractMetaData objectType)
395: throws ObjectAlreadyExistsException {
396:
397: String key = objectType.getId();
398: if (_objectTypes.containsKey(key)) {
399: throw new ObjectAlreadyExistsException(
400: "Repository already contains type with key: " + key);
401: }
402: if (getLogger().isDebugEnabled())
403: getLogger().debug("registerObjectType(" + objectType + ')');
404: else
405: getLogger().info("Registered object type: " + key);
406: _objectTypes.put(key, objectType);
407: }
408:
409: protected void createEntry(AbstractMetaData metaData)
410: throws RepositoryException {
411:
412: createEntry(metaData.getId(), metaData, null);
413: }
414:
415: protected synchronized Entry createEntry(String key,
416: AbstractMetaData metaData, Object instance)
417: throws RepositoryException {
418:
419: if (key == null) {
420: throw new RepositoryException(
421: "null key for createEntry(null, " + metaData + ", "
422: + instance + ')');
423: }
424: if (_entries.containsKey(key)) {
425: throw new ObjectAlreadyExistsException(
426: "Repository already contains key: " + key);
427: }
428: if (metaData == null && instance == null) {
429: throw new IllegalArgumentException(
430: "metaData and instance cannot both be null");
431: }
432:
433: Entry entry = new Entry(metaData, instance);
434:
435: if (getLogger().isDebugEnabled()) {
436: if (metaData != null) {
437: getLogger().debug(
438: "createEntry(" + metaData + ", " + instance
439: + ')');
440: } else {
441: getLogger().debug(
442: "createEntry(" + key + "->" + instance + ')');
443: }
444: } else {
445: getLogger().info("Created entry: " + key);
446: }
447:
448: _entries.put(key, entry);
449: _metaData = null;
450: _modified = true;
451:
452: return entry;
453: }
454:
455: protected synchronized void deleteEntry(String key)
456: throws RepositoryException {
457:
458: if (!_entries.containsKey(key))
459: throw new ObjectNotFoundException(NOT_FOUND_MSG + key);
460:
461: if (getLogger().isDebugEnabled()) {
462: getLogger().debug("deleteEntry(" + key + +')');
463: }
464:
465: _entries.remove(key);
466: _metaData = null;
467: _modified = true;
468: }
469:
470: protected Entry updateEntry(String key, AbstractMetaData metaData)
471: throws RepositoryException {
472:
473: return updateEntry(key, metaData, null);
474: }
475:
476: protected synchronized Entry updateEntry(String key,
477: AbstractMetaData metaData, Object implementation)
478: throws RepositoryException {
479:
480: if (!_entries.containsKey(key)) {
481: throw new ObjectNotFoundException(NOT_FOUND_MSG + key);
482: }
483:
484: if (getLogger().isDebugEnabled()) {
485: getLogger().debug(
486: "updateEntry(" + key + ", " + metaData + ", "
487: + implementation + ')');
488: }
489:
490: Entry entry = new Entry(metaData, implementation);
491: _entries.put(key, entry);
492: _metaData = null;
493: _modified = true;
494: return entry;
495: }
496:
497: protected AbstractMetaData[] findObjectTypes() {
498: Collection coll = _objectTypes.values();
499: AbstractMetaData[] array = (AbstractMetaData[]) Array
500: .newInstance(_metaDataClass, coll.size());
501: return (AbstractMetaData[]) coll.toArray(array);
502: }
503:
504: protected AbstractMetaData findObjectType(String className)
505: throws RepositoryException {
506:
507: AbstractMetaData objType = (AbstractMetaData) _objectTypes
508: .get(className);
509: if (objType == null)
510: throw new ObjectNotFoundException(className);
511: return objType;
512: }
513:
514: protected Entry[] findEntries() {
515: Collection entries = _entries.values();
516: return (Entry[]) entries.toArray(new Entry[entries.size()]);
517: }
518:
519: protected synchronized AbstractMetaData[] findMetaData() {
520: // Object[] findMetaData can return null elements if implementation
521: // objects were added directly (without using meta data), so we must
522: // build this list dynamically.
523: AbstractMetaData[] metaData = _metaData;
524: if (metaData == null) {
525: List list = new ArrayList(_entries.size());
526: for (Iterator iter = _entries.values().iterator(); iter
527: .hasNext();) {
528: Entry entry = (Entry) iter.next();
529: if (entry._metadata != null)
530: list.add(entry._metadata);
531: }
532: metaData = (AbstractMetaData[]) Array.newInstance(
533: _metaDataClass, list.size());
534: _metaData = (AbstractMetaData[]) list.toArray(metaData);
535: }
536: return metaData;
537: }
538:
539: protected Entry findEntry(String key, boolean throwException)
540: throws RepositoryException {
541:
542: Entry entry = (Entry) _entries.get(key);
543: if (entry == null && throwException) {
544: throw new ObjectNotFoundException(NOT_FOUND_MSG + key);
545: }
546: return entry;
547: }
548:
549: protected AbstractMetaData findMetaData(String key,
550: boolean throwException) throws RepositoryException {
551:
552: // Object findMetaData can return null if implementation
553: // objects were added directly (without using meta data)
554: Entry entry = findEntry(key, throwException);
555: return entry == null ? null : entry.getMetaData();
556: }
557:
558: protected Object findInstance(String key, boolean throwException)
559: throws RepositoryException {
560:
561: Entry entry = findEntry(key, throwException);
562: return entry == null ? null : entry.getInstance(_svcMgr
563: .getResourceRepository());
564: }
565:
566: protected abstract Log getLogger();
567:
568: public final ServiceManager getServiceManager() {
569: return _svcMgr;
570: }
571: }
|