001: // ResourceStoreImpl.java
002: // $Id: ResourceStoreImpl.java,v 1.17 2007/02/09 22:29:17 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.tools.resources.store;
007:
008: import org.w3c.tools.resources.AttributeHolder;
009: import org.w3c.tools.resources.InvalidResourceException;
010: import org.w3c.tools.resources.Resource;
011:
012: import org.w3c.tools.resources.serialization.Serializer;
013: import org.w3c.tools.resources.serialization.SerializationException;
014: import org.w3c.util.EmptyEnumeration;
015:
016: import java.util.Enumeration;
017: import java.util.Hashtable;
018: import java.util.Vector;
019:
020: import java.io.BufferedReader;
021: import java.io.BufferedWriter;
022: import java.io.File;
023: import java.io.FileReader;
024: import java.io.FileOutputStream;
025: import java.io.OutputStreamWriter;
026: import java.io.IOException;
027: import java.io.PrintStream;
028: import java.io.Reader;
029: import java.io.Writer;
030:
031: /**
032: * A generic resource store that keeps resource in a file using
033: * the Serializer interface.
034: */
035:
036: public class ResourceStoreImpl implements ResourceStore {
037:
038: static final int writerSize = 65536;
039:
040: class ResourceIndex {
041:
042: boolean modified = false;
043: String identifier = null;
044: Resource resource = null;
045: boolean initialized = false;
046:
047: synchronized void markModified() {
048: modified = true;
049: }
050:
051: synchronized boolean isModified() {
052: return modified;
053: }
054:
055: synchronized Resource loadResource(Hashtable defs) {
056: if (initialized) {
057: return resource;
058: } else {
059: resource.initialize(defs);
060: initialized = true;
061: return resource;
062: }
063: }
064:
065: synchronized Resource getResource() {
066: return resource;
067: }
068:
069: void unloadResource() {
070: // notify the resource of unload:
071: resource.notifyUnload();
072: resource = null;
073: }
074:
075: synchronized String getIdentifier() {
076: return identifier;
077: }
078:
079: synchronized void setIdentifier(String identifier) {
080: this .identifier = identifier;
081: }
082:
083: ResourceIndex(Resource resource, boolean initialized) {
084: this .resource = resource;
085: this .identifier = resource.unsafeGetIdentifier();
086: this .modified = false;
087: this .initialized = initialized;
088: }
089:
090: }
091:
092: /**
093: * The store format version number.
094: */
095: private static final int VERSION = 2;
096: /**
097: * Our Resource Serializer.
098: */
099: protected Serializer serializer = null;
100: /**
101: * Our underlying associated file.
102: */
103: File repository = null;
104: /**
105: * Our resource store manager.
106: */
107: protected ResourceStoreManager manager = null;
108: /**
109: * Our token within the resource store manager.
110: */
111: Object token = null;
112: /**
113: * The resources we know about: maps identifier to resource objects.
114: */
115: Hashtable resources = null;
116: /**
117: * Has this repository been modified.
118: */
119: boolean modified = false;
120:
121: /**
122: * Mark the store as having been used recently.
123: */
124:
125: protected final void markUsed() {
126: if (manager != null)
127: manager.markUsed(token);
128: }
129:
130: // be smart here
131: // FIXME removed the synchronized to avoid a deadlock
132: protected void markModified() {
133: if (!modified) {
134: synchronized (this ) {
135: modified = true;
136: }
137: }
138: }
139:
140: /**
141: * Get the version of that resource store.
142: * Version numbers are used to distinguish between pickling format.
143: * A resource store implementator has the duty of bumping the returned
144: * number whenever it's archiving format changes.
145: * Resource stores that relies on some external archiving mechanisms
146: * (such as a database), may return a constant.
147: * @return An integer version number.
148: */
149:
150: public int getVersion() {
151: return VERSION;
152: }
153:
154: /**
155: * Get the identifier for that store.
156: * @return A uniq store identifier, as a String.
157: */
158:
159: public String getIdentifier() {
160: return repository.getAbsolutePath();
161: }
162:
163: /**
164: * Emit the given string as a warning, to whoever it is appropriate.
165: * @param msg The warning message.
166: */
167:
168: protected void warning(String msg) {
169: System.out.println("[" + getClass().getName() + "@"
170: + repository + "]: " + msg);
171: }
172:
173: /**
174: * Restore the resource whose name is given.
175: * This method doesn't assume that the resource will actually be restored,
176: * it can be kept in a cache by the ResourceStore object, and the cached
177: * version of the resource may be returned.
178: * @param identifier The identifier of the resource to restore.
179: * @param defs Default attribute values. If the resource needs to be
180: * restored from its pickled version, this Hashtable provides
181: * a set of default values for some of the attributes.
182: * @return A Resource instance, or <strong>null</strong>.
183: * @exception InvalidResourceException If the resource could not
184: * be restored from the store.
185: */
186:
187: public Resource loadResource(String identifier, Hashtable defs)
188: throws InvalidResourceException {
189: loadResources();
190: markUsed();
191: ResourceIndex index = (ResourceIndex) resources.get(identifier);
192: if (index == null)
193: return null;
194: if (defs == null)
195: defs = new Hashtable(3);
196: defs.put("store-entry", index);
197: return index.loadResource(defs);
198: }
199:
200: /**
201: * Get this resource, but only if already loaded.
202: * The resource store may (recommended) maintain a cache of the resource
203: * it loads from its store. If the resource having this identifier
204: * has already been loaded, return it, otherwise, return
205: * <strong>null</strong>.
206: * @param identifier The resource identifier.
207: * @return A Resource instance, or <strong>null</strong>.
208: */
209:
210: public Resource lookupResource(String identifier) {
211: loadResources();
212: markUsed();
213: ResourceIndex index = (ResourceIndex) resources.get(identifier);
214: return (((index == null) || (!index.initialized)) ? null
215: : index.getResource());
216: }
217:
218: /**
219: * Stabilize the given resource.
220: * @param resource The resource to save.
221: */
222:
223: public void saveResource(Resource resource) {
224: loadResources();
225: ResourceIndex index = (ResourceIndex) resource.getStoreEntry();
226: if (index == null)
227: throw new UnknownResourceException(resource);
228: if (index.isModified())
229: save();
230: markUsed();
231: }
232:
233: /**
234: * Add this resource to this resource store.
235: * @param resource The resource to be added.
236: */
237:
238: public synchronized void addResource(Resource resource) {
239: loadResources();
240: ResourceIndex index = new ResourceIndex(resource, true);
241: index.markModified();
242: resource.setValue("store-entry", index);
243: resources.put(index.getIdentifier(), index);
244: markModified();
245: markUsed();
246: }
247:
248: /**
249: * Remove this resource from the repository.
250: * @param identifier The identifier of the resource to be removed.
251: */
252:
253: public synchronized void removeResource(String identifier) {
254: ResourceIndex index = (ResourceIndex) resources.get(identifier);
255: if (index != null) {
256: index.unloadResource();
257: resources.remove(identifier);
258: markModified();
259: markUsed();
260: }
261: }
262:
263: /**
264: * Rename a given resource.
265: * @param oldid The olde resource identifier.
266: * @param newid The new resource identifier.
267: */
268:
269: public synchronized void renameResource(String oldid, String newid) {
270: ResourceIndex index = (ResourceIndex) resources.get(oldid);
271: if (index != null) {
272: resources.remove(oldid);
273: index.setIdentifier(newid);
274: resources.put(newid, index);
275: index.markModified();
276: markModified();
277: }
278: }
279:
280: /**
281: * Mark this resource as modified.
282: * @param resource The resource that has changed (and will have to be
283: * pickled some time latter).
284: */
285:
286: public void markModified(Resource resource) {
287: ResourceIndex index = (ResourceIndex) resource.getStoreEntry();
288: if (index != null) {
289: index.markModified();
290: markModified();
291: markUsed();
292: }
293: }
294:
295: /**
296: * Can this resource store be unloaded now ?
297: * This method gets called by the ResourceStoreManager before calling
298: * the <code>shutdown</code> method, when possible. An implementation
299: * of that method is responsible for checking the <code>acceptUnload
300: * </code> method of all its loaded resource before returning
301: * <strong>true</strong>, meaning that the resource store can be unloaded.
302: * @return A boolean <strong>true</strong> if the resource store can be
303: * unloaded.
304: */
305:
306: public synchronized boolean acceptUnload() {
307: if (resources == null) {
308: return true;
309: }
310: boolean accept = true;
311: if ((manager != null) && (manager.getStoreSizeLimit() > 0)
312: && resources.size() > manager.getStoreSizeLimit()) {
313: accept = false;
314: } else {
315: Enumeration e = resources.elements();
316: while (e.hasMoreElements()) {
317: ResourceIndex entry = (ResourceIndex) e.nextElement();
318: Resource resource = entry.getResource();
319: synchronized (entry) {
320: if (!resource.acceptUnload())
321: accept = false;
322: }
323: }
324: }
325: if (!accept) {
326: try {
327: if (modified)
328: internalSave(false);
329: } catch (IOException ex) {
330: ex.printStackTrace();
331: warning("internalSave failed at acceptUnload");
332: }
333: }
334: return accept;
335: }
336:
337: /**
338: * Internal save: save the repository back to disk.
339: * @param unload Should we unload any existing resources ?
340: */
341:
342: protected synchronized void internalSave(boolean unload)
343: throws IOException {
344: //nothing to save
345: if (resources == null) {
346: return;
347: }
348: //1st, build the resource array
349: Enumeration e = resources.elements();
350: Vector vres = new Vector(11);
351: while (e.hasMoreElements()) {
352: Resource res = ((ResourceIndex) e.nextElement())
353: .getResource();
354: vres.addElement(res);
355: }
356: Resource resourcearray[] = new Resource[vres.size()];
357: vres.copyInto(resourcearray);
358:
359: //try to save in a temporary file
360: File tmp = new File(repository.getParent(), repository
361: .getName()
362: + ".tmp");
363: FileOutputStream fos = new FileOutputStream(tmp);
364: OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
365: Writer writer = new BufferedWriter(osw, writerSize);
366:
367: serializer.writeResources(resourcearray, writer);
368:
369: //success!!
370: String name = repository.getName();
371: String dir = repository.getParent();
372: File bak = new File(dir, name + ".bak");
373: File tild = new File(dir, name + ".bak~");
374: // 1st move: delete the ~ file if any:
375: if (tild.exists())
376: tild.delete();
377: // 2nd move rename bak to ~ (bak no longer exists)
378: if (bak.exists()) {
379: bak.renameTo(tild);
380: bak.delete();
381: }
382: // 3nd move: rename the current repository to bak
383: if (repository.exists()) {
384: if (!repository.renameTo(bak)) {
385: warning("unable to rename " + repository + " to " + bak);
386: tild.renameTo(bak);
387: }
388: repository.delete();
389: }
390: // 4th move: rename the tmp file to the repository
391: if (!tmp.renameTo(repository)) {
392: bak.renameTo(repository);
393: tild.renameTo(bak);
394: warning("unable to rename " + tmp + " to " + repository);
395: }
396: // cleanup (erase the ~ file)
397: tild.delete();
398: modified = false;
399: //unload if needed
400: if (unload) {
401: for (int i = 0; i < resourcearray.length; i++)
402: resourcearray[i].notifyUnload();
403: resources = null;
404: }
405: }
406:
407: /**
408: * Shutdown this store.
409: */
410:
411: public synchronized void shutdown() {
412: if (modified) {
413: try {
414: internalSave(true);
415: } catch (IOException ex) {
416: ex.printStackTrace();
417: warning("internalSave failed at shutdown.");
418: }
419: } else {
420: // Just notify the unload of loaded resources:
421: if (resources != null) {
422: Enumeration entries = resources.elements();
423: while (entries.hasMoreElements()) {
424: ResourceIndex index = (ResourceIndex) entries
425: .nextElement();
426: index.unloadResource();
427: }
428: }
429: }
430: // Clean-up all references we have to external objects:
431: resources = null;
432: manager = null;
433: }
434:
435: /**
436: * Save this store.
437: */
438:
439: public void save() {
440: if (modified) {
441: try {
442: internalSave(false);
443: } catch (IOException ex) {
444: warning("Save failed (IO) [" + ex.getMessage() + "]");
445: } catch (Exception oex) {
446: warning("Save failed [" + oex.getMessage() + "]");
447: }
448: }
449: }
450:
451: /**
452: * Enumerate all the resources saved in this store.
453: * @return An enumeration of Strings, giving the identifier for all
454: * the resources that this store knows about.
455: */
456:
457: public Enumeration enumerateResourceIdentifiers() {
458: markUsed();
459: loadResources();
460: return resources.keys();
461: }
462:
463: /**
464: * Check for the existence of a resource in this store.
465: * @param identifier The identifier of the resource to check.
466: * @return A boolean <strong>true</strong> if the resource exists
467: * in this store, <strong>false</strong> otherwise.
468: */
469:
470: public boolean hasResource(String identifier) {
471: markUsed();
472: loadResources();
473: return resources.get(identifier) != null;
474: }
475:
476: protected synchronized void loadResources() {
477: int i = 0;
478: if (resources == null) {
479: try {
480: resources = new Hashtable(11);
481: if (repository.exists()) {
482: Reader reader = new BufferedReader(new FileReader(
483: repository));
484: Resource resourceArray[] = serializer
485: .readResources(reader);
486: for (i = 0; i < resourceArray.length; i++) {
487: ResourceIndex entry = new ResourceIndex(
488: resourceArray[i], false);
489: if (entry != null
490: && entry.getIdentifier() != null) {
491: resources.put(entry.getIdentifier(), entry);
492: }
493: }
494: }
495: } catch (IOException ioex) {
496: ioex.printStackTrace();
497: warning("Unable to load resources");
498: } catch (SerializationException sex) {
499: warning(sex.getMessage());
500: sex.printStackTrace();
501: } catch (Exception ex) {
502: ex.printStackTrace();
503: String err = "Error in " + repository.getName()
504: + " in dir " + repository.getParent() + ": ["
505: + i + "] " + ex.getMessage();
506: warning(err);
507: }
508: }
509: }
510:
511: /**
512: * This resource store is being built, initialize it with the given arg.
513: * @param manager The ResourceStoreManager instance that asks yourself
514: * to initialize.
515: * @param token The resource store manager key to that resource store,
516: * this token should be used when calling methods from the manager that
517: * are to act on yourself.
518: * @param repository A file, giving the location of the associated
519: * repository.
520: */
521: public void initialize(ResourceStoreManager manager, Object token,
522: File repository, Serializer serializer) {
523: this.manager = manager;
524: this.token = token;
525: this.repository = repository;
526: this.serializer = serializer;
527: this.resources = null;
528: markUsed();
529: }
530:
531: }
|