001: /*---------------------------------------------------------------------------*\
002: $Id: DataPersister.java 6498 2006-10-02 21:13:33Z bmc $
003: \*---------------------------------------------------------------------------*/
004:
005: package org.clapper.curn;
006:
007: import java.net.URL;
008: import java.util.ArrayList;
009: import java.util.Collection;
010: import java.util.HashMap;
011: import java.util.Map;
012: import org.clapper.util.logging.Logger;
013:
014: /**
015: * Persists data to the <i>curn</i> persistent data store, whatever that may
016: * be.
017: *
018: * @version <tt>$Revision: 6498 $</tt>
019: */
020: public abstract class DataPersister {
021: /*----------------------------------------------------------------------*\
022: Public Interfaces
023: \*----------------------------------------------------------------------*/
024:
025: /**
026: * Used to define a callback for handling loaded data.
027: */
028: public interface LoadedDataHandler {
029: /**
030: * Called by the subclass when it has finished loading data for a feed.
031: *
032: * @param feedData the loaded feed (and item) data
033: *
034: * @throws CurnException on error
035: */
036: public void feedLoaded(PersistentFeedData feedData)
037: throws CurnException;
038:
039: /**
040: * Called by the subclass when it has finished loading the extra
041: * metadata for a particular namespace.
042: *
043: * @param metadataGroup the metadata for the namespace
044: *
045: * @throws CurnException on error
046: */
047: public void extraMetadataLoaded(
048: PersistentMetadataGroup metadataGroup)
049: throws CurnException;
050: }
051:
052: /*----------------------------------------------------------------------*\
053: Private Data Items
054: \*----------------------------------------------------------------------*/
055:
056: /**
057: * Map of interested PersistentDataClient objects, indexed by namespace
058: */
059: private Map<String, PersistentDataClient> persistentDataClients = new HashMap<String, PersistentDataClient>();
060:
061: /**
062: * For logging
063: */
064: private static final Logger log = new Logger(DataPersister.class);
065:
066: /*----------------------------------------------------------------------*\
067: Constructor
068: \*----------------------------------------------------------------------*/
069:
070: protected DataPersister() {
071: }
072:
073: /*----------------------------------------------------------------------*\
074: Public Methods
075: \*----------------------------------------------------------------------*/
076:
077: /**
078: * Save the feed metadata. The configuration is passed in, so that
079: * the persister can obtain, from the configuration, whatever
080: * data it needs to find the persisted metadata to read.
081: *
082: * @param feedCache {@link FeedCache} object to save
083: *
084: * @throws CurnException on error
085: */
086: public final void saveData(FeedCache feedCache)
087: throws CurnException {
088: if (isEnabled()) {
089: // First, retrieve all entries from the cache and reorganize them.
090:
091: Collection<FeedCacheEntry> cacheEntries = feedCache
092: .getAllEntries();
093: Map<URL, PersistentFeedData> cacheDataByFeed = getCacheDataByFeed(cacheEntries);
094:
095: // Now that everything's in the right order, gather the additional
096: // metadata for each feed and its items. We don't need the map
097: // any more.
098:
099: Collection<PersistentFeedData> persistentDataByFeed = cacheDataByFeed
100: .values();
101: cacheDataByFeed = null;
102:
103: for (PersistentFeedData feedData : persistentDataByFeed)
104: getFeedMetadataForFeed(feedData);
105:
106: // Now, gather any extra metadata that isn't attached to a feed or
107: // item.
108:
109: Collection<PersistentMetadataGroup> extraMetadata = new ArrayList<PersistentMetadataGroup>();
110:
111: for (PersistentDataClient client : persistentDataClients
112: .values()) {
113: String namespace = client.getMetatdataNamespace();
114: PersistentMetadataGroup metadata;
115: Map<String, String> nameValuePairs = client
116: .getExtraFeedMetadata();
117: if ((nameValuePairs != null)
118: && (nameValuePairs.size() > 0)) {
119: metadata = new PersistentMetadataGroup(namespace);
120: metadata.addMetadata(nameValuePairs);
121: extraMetadata.add(metadata);
122: }
123: }
124:
125: // Let the saving begin.
126:
127: startSaveOperation();
128:
129: for (PersistentFeedData feedData : persistentDataByFeed)
130: saveFeedData(feedData);
131:
132: saveExtraMetadata(extraMetadata);
133: endSaveOperation();
134: }
135: }
136:
137: /**
138: * Load the cache and metadata.
139: *
140: * @param feedCache the {@link FeedCache} object to fill
141: *
142: * @throws CurnException on error
143: */
144: public void loadData(final FeedCache feedCache)
145: throws CurnException {
146: if (isEnabled()) {
147: startLoadOperation();
148:
149: doLoad(new LoadedDataHandler() {
150: public void feedLoaded(PersistentFeedData feedData)
151: throws CurnException {
152: processLoadedFeed(feedData, feedCache);
153: }
154:
155: public void extraMetadataLoaded(
156: PersistentMetadataGroup metadataGroup)
157: throws CurnException {
158: String namespace = metadataGroup.getNamespace();
159: PersistentDataClient client = persistentDataClients
160: .get(namespace);
161: if (client == null) {
162: log
163: .warn("No plug-in or other class has registered "
164: + "interest in extra metadata namespace \""
165: + namespace
166: + "\". "
167: + "Ignoring the metadata.");
168: }
169:
170: else {
171: Map<String, String> nameValuePairs = metadataGroup
172: .getMetadata();
173: for (Map.Entry<String, String> entry : nameValuePairs
174: .entrySet()) {
175: client.parseExtraMetadata(entry.getKey(),
176: entry.getValue());
177: }
178: }
179: }
180: });
181:
182: endLoadOperation();
183: feedCache.optimizeAfterLoad();
184: }
185: }
186:
187: /**
188: * Register a {@link PersistentDataClient} object with this registry.
189: * When data for that client is read, this object will call the
190: * client's <tt>process</tt> methods. When it's time to save the metadata,
191: * this object will call the client's <tt>get</tt> methods. Multiple
192: * {@link PersistentDataClient} objects may be registered with this object.
193: *
194: * @param client the {@link PersistentDataClient} object
195: */
196: public final void addPersistentDataClient(
197: PersistentDataClient client) {
198: persistentDataClients.put(client.getMetatdataNamespace(),
199: client);
200: }
201:
202: /**
203: * Called when the <tt>DataPersister</tt> is first instantiated. Useful
204: * for retrieving configuration values, etc.
205: *
206: * @param curnConfig the configuration
207: *
208: * @throws CurnException on error
209: */
210: public abstract void init(CurnConfig curnConfig)
211: throws CurnException;
212:
213: /*----------------------------------------------------------------------*\
214: Protected Methods
215: \*----------------------------------------------------------------------*/
216:
217: /**
218: * Determine whether the data persister subclass is enabled or not (i.e.,
219: * whether or not metadata is to be loaded and saved). The configuration
220: * usually determines whether or not the data persister is enabled.
221: *
222: * @return <tt>true</tt> if enabled, <tt>false</tt> if disabled.
223: */
224: protected abstract boolean isEnabled();
225:
226: /**
227: * Called at the beginning of the load operation to initialize
228: * the load.
229: *
230: * @throws CurnException on error
231: */
232: protected abstract void startLoadOperation() throws CurnException;
233:
234: /**
235: * Called at the end of the load operation to close files, clean
236: * up, etc.
237: *
238: * @throws CurnException on error
239: */
240: protected abstract void endLoadOperation() throws CurnException;
241:
242: /**
243: * The actual load method; only called if the object is enabled.
244: *
245: * @param loadedDataHandler object to receive data as it's loaded
246: *
247: * @throws CurnException on error
248: */
249: protected abstract void doLoad(LoadedDataHandler loadedDataHandler)
250: throws CurnException;
251:
252: /**
253: * Called at the beginning of the actual save operation to initialize
254: * the save, etc.
255: *
256: * @throws CurnException on error
257: */
258: protected abstract void startSaveOperation() throws CurnException;
259:
260: /**
261: * Called at the end of the actual save operation to flush files, clean
262: * up, etc.
263: *
264: * @throws CurnException on error
265: */
266: protected abstract void endSaveOperation() throws CurnException;
267:
268: /**
269: * Save the data for one feed, including the items.
270: *
271: * @param feedData the feed data to be saved
272: *
273: * @throws CurnException on error
274: */
275: protected abstract void saveFeedData(PersistentFeedData feedData)
276: throws CurnException;
277:
278: /**
279: * Save any extra metadata (i.e., metadata that isn't attached to a
280: * specific feed or a specific item).
281: *
282: * @param metadata the collection of metadata items
283: *
284: * @throws CurnException on error
285: */
286: protected abstract void saveExtraMetadata(
287: Collection<PersistentMetadataGroup> metadata)
288: throws CurnException;
289:
290: /*----------------------------------------------------------------------*\
291: Private Methods
292: \*----------------------------------------------------------------------*/
293:
294: /**
295: * Get the persistent metadata for one feed. Also handles getting
296: * the data for the items.
297: *
298: * @param feedData the PersistentFeedData object into which to store
299: * the metadata
300: *
301: * @throws CurnException on error
302: */
303: private void getFeedMetadataForFeed(PersistentFeedData feedData)
304: throws CurnException {
305: for (PersistentDataClient client : persistentDataClients
306: .values()) {
307: String namespace = client.getMetatdataNamespace();
308: PersistentMetadataGroup metadata;
309:
310: // First the feed-specific metadata
311:
312: FeedCacheEntry feedCacheEntry = feedData
313: .getFeedCacheEntry();
314: Map<String, String> nameValuePairs = client
315: .getMetadataForFeed(feedCacheEntry);
316: if ((nameValuePairs != null) && (nameValuePairs.size() > 0)) {
317: metadata = new PersistentMetadataGroup(namespace);
318: metadata.addMetadata(nameValuePairs);
319: feedData.addFeedMetadataGroup(metadata);
320: }
321:
322: // Now the metadata for each item.
323:
324: for (PersistentFeedItemData itemData : feedData
325: .getPersistentFeedItems()) {
326: FeedCacheEntry itemCacheEntry = itemData
327: .getFeedCacheEntry();
328: nameValuePairs = client.getMetadataForItem(
329: itemCacheEntry, feedCacheEntry);
330: if ((nameValuePairs != null)
331: && (nameValuePairs.size() > 0)) {
332: metadata = new PersistentMetadataGroup(namespace);
333: metadata.addMetadata(nameValuePairs);
334: itemData.addItemMetadataGroup(metadata);
335: }
336: }
337: }
338: }
339:
340: private Map<URL, PersistentFeedData> getCacheDataByFeed(
341: final Collection<FeedCacheEntry> cacheEntries) {
342:
343: Map<URL, PersistentFeedData> cacheDataByFeed = new HashMap<URL, PersistentFeedData>();
344:
345: for (FeedCacheEntry entry : cacheEntries) {
346: URL channelURL = entry.getChannelURL();
347: PersistentFeedData feedData = cacheDataByFeed
348: .get(channelURL);
349: if (feedData == null) {
350: feedData = new PersistentFeedData();
351: cacheDataByFeed.put(channelURL, feedData);
352: }
353:
354: if (entry.isChannelEntry()) {
355: feedData.setFeedCacheEntry(entry);
356: }
357:
358: else // It's an item entry
359: {
360: PersistentFeedItemData itemData = new PersistentFeedItemData(
361: entry);
362: feedData.addPersistentFeedItem(itemData);
363: }
364: }
365: return cacheDataByFeed;
366: }
367:
368: private void processLoadedFeed(final PersistentFeedData feedData,
369: final FeedCache feedCache) throws CurnException {
370: // Add the feed cache entry to the feed cache.
371:
372: FeedCacheEntry feedCacheEntry = feedData.getFeedCacheEntry();
373: log.debug("processLoadedFeed: Processing loaded feed data for "
374: + feedCacheEntry.getChannelURL());
375: feedCache.loadFeedCacheEntry(feedCacheEntry);
376:
377: // Dispatch the feed metadata to the appropriate places.
378:
379: for (PersistentMetadataGroup mg : feedData.getFeedMetadata()) {
380: String namespace = mg.getNamespace();
381: PersistentDataClient client = persistentDataClients
382: .get(namespace);
383: if (client == null) {
384: log
385: .warn("No plug-in or other class has registered "
386: + "interest in feed metadata namespace \""
387: + namespace
388: + "\". "
389: + "Ignoring the metadata.");
390: }
391:
392: else {
393: log.debug("Dispatching feed metadata in namespace \""
394: + namespace + "\" to instance of class "
395: + client.getClass().toString());
396: Map<String, String> nameValuePairs = mg.getMetadata();
397: for (Map.Entry<String, String> entry : nameValuePairs
398: .entrySet()) {
399: client.parseFeedMetadata(entry.getKey(), entry
400: .getValue(), feedCacheEntry);
401: }
402: }
403: }
404:
405: // Process the items.
406:
407: for (PersistentFeedItemData itemData : feedData
408: .getPersistentFeedItems()) {
409: // Add the item's feed cache entry to the cache.
410:
411: FeedCacheEntry itemCacheEntry = itemData
412: .getFeedCacheEntry();
413: log.debug("processLoadedFeed: adding item "
414: + itemData.getFeedCacheEntry().getEntryURL()
415: + " to cache.");
416: feedCache.loadFeedCacheEntry(itemCacheEntry);
417: log.debug("processLoadedFeed: Processing item metadata");
418:
419: // Now process the metadata.
420:
421: for (PersistentMetadataGroup mg : feedData
422: .getFeedMetadata()) {
423: String namespace = mg.getNamespace();
424: PersistentDataClient client = persistentDataClients
425: .get(namespace);
426: if (client == null) {
427: log
428: .warn("No plug-in or other class has registered "
429: + "interest in item metadata namespace \""
430: + namespace
431: + "\". "
432: + "Ignoring the metadata.");
433: }
434:
435: else {
436: log
437: .debug("Dispatching item metadata in namespace \""
438: + namespace
439: + "\" to instance of class "
440: + client.getClass().toString());
441: Map<String, String> nameValuePairs = mg
442: .getMetadata();
443: for (Map.Entry<String, String> entry : nameValuePairs
444: .entrySet()) {
445: client.parseItemMetadata(entry.getKey(), entry
446: .getValue(), itemCacheEntry);
447: }
448: }
449: }
450: }
451: }
452: }
|