001: /*
002: * Copyright 2004 Outerthought bvba and Schaubroeck nv
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.outerj.daisy.books.store.impl;
017:
018: import org.outerj.daisy.books.store.*;
019: import org.outerj.daisy.xmlutil.LocalSAXParserFactory;
020: import org.apache.xmlbeans.XmlOptions;
021: import org.apache.xmlbeans.XmlObject;
022: import org.outerx.daisy.x10Bookstoremeta.ResourcePropertiesDocument;
023:
024: import java.io.*;
025: import java.net.URI;
026: import java.util.Date;
027: import java.util.Collection;
028: import java.util.List;
029: import java.util.ArrayList;
030:
031: /**
032: * A book instance. Instances of this object can be used by multiple users/thread, this
033: * object is wrapped inside a {@link UserBookInstance} by the {@link CommonBookStore}
034: * before returning it to a particular user.
035: */
036: public class CommonBookInstance implements BookInstance {
037: private final File bookInstanceDirectory;
038: private final AclResource aclResource;
039: private final PublicationsInfoResource publicationsInfoResource;
040: private final MetaDataResource metaDataResource;
041: private final CommonBookStore owner;
042:
043: private static final int BUFFER_SIZE = 32768;
044: static final String ACL_FILENAME = "acl.xml";
045: static final String PUBLICATIONS_INFO_FILENAME = "publications_info.xml";
046: static final String METADATA_FILENAME = "metadata.xml";
047: static final String[] META_FILES = { ACL_FILENAME,
048: PUBLICATIONS_INFO_FILENAME, METADATA_FILENAME };
049: static final String LOCK_FILE_NAME = "lock";
050:
051: protected CommonBookInstance(File bookInstanceDirectory,
052: CommonBookStore owner) {
053: this .bookInstanceDirectory = bookInstanceDirectory;
054: this .owner = owner;
055: this .aclResource = new AclResource(new File(
056: bookInstanceDirectory, ACL_FILENAME));
057: this .publicationsInfoResource = new PublicationsInfoResource(
058: new File(bookInstanceDirectory,
059: PUBLICATIONS_INFO_FILENAME));
060: this .metaDataResource = new MetaDataResource(new File(
061: bookInstanceDirectory, METADATA_FILENAME));
062: }
063:
064: File getDirectory() {
065: return bookInstanceDirectory;
066: }
067:
068: public String getName() {
069: return bookInstanceDirectory.getName();
070: }
071:
072: public InputStream getResource(String path) {
073: File file = getFile(path);
074:
075: FileInputStream fis;
076: try {
077: fis = new FileInputStream(file);
078: } catch (FileNotFoundException e) {
079: throw new BookResourceNotFoundException(
080: "Resource not found: " + path);
081: }
082: return new BufferedInputStream(fis);
083: }
084:
085: public ResourcePropertiesDocument getResourceProperties(String path) {
086: String metaFileName = path + ".meta.xml";
087: File metaFile = getFile(metaFileName);
088: if (metaFile.exists()) {
089: try {
090: XmlOptions xmlOptions = new XmlOptions()
091: .setLoadUseXMLReader(LocalSAXParserFactory
092: .newXmlReader());
093: ResourcePropertiesDocument propertiesDocument = ResourcePropertiesDocument.Factory
094: .parse(metaFile, xmlOptions);
095: return propertiesDocument;
096: } catch (Exception e) {
097: throw new RuntimeException(
098: "Error reading resource properties.", e);
099: }
100: }
101: return null;
102: }
103:
104: public void storeResourceProperties(String path,
105: ResourcePropertiesDocument resourcePropertiesDocument) {
106: XmlOptions xmlOptions = new XmlOptions();
107: xmlOptions.setSavePrettyPrint();
108: xmlOptions.setUseDefaultNamespace();
109: storeResource(path + ".meta.xml", resourcePropertiesDocument
110: .newInputStream(xmlOptions));
111: }
112:
113: public void storeResource(String path, InputStream is) {
114: BookStoreException exception = null;
115: try {
116: File file = getFileForStorage(path);
117: FileOutputStream fos = null;
118: try {
119: try {
120: fos = new FileOutputStream(file);
121: BufferedOutputStream bos = new BufferedOutputStream(
122: fos);
123: byte[] buffer = new byte[BUFFER_SIZE];
124: int read;
125: while ((read = is.read(buffer)) != -1) {
126: bos.write(buffer, 0, read);
127: }
128: bos.flush();
129: fos.getFD().sync();
130: } finally {
131: if (fos != null)
132: fos.close();
133: }
134: } catch (IOException e) {
135: exception = new BookStoreException(
136: "Error storing resource in book instance.", e);
137: throw exception;
138: }
139: } finally {
140: try {
141: is.close();
142: } catch (IOException e) {
143: if (exception != null) {
144: // don't hide original exception
145: throw new BookStoreException(
146: "Error closing input stream, but also got an earlier exception.",
147: exception);
148: } else {
149: throw new BookStoreException(
150: "Error closing input stream.", e);
151: }
152: }
153: }
154: }
155:
156: public OutputStream getResourceOutputStream(String path)
157: throws IOException {
158: File file = getFileForStorage(path);
159: return new BufferedOutputStream(new FileOutputStream(file));
160: }
161:
162: public boolean rename(String path, String newName) {
163: if (path == null)
164: throw new IllegalArgumentException(
165: "path argument cannot be null");
166: if (newName == null)
167: throw new IllegalArgumentException(
168: "newName argument cannot be null");
169: if (newName.indexOf(System.getProperty("file.separator")) != -1)
170: throw new IllegalArgumentException(
171: "New name may not contain a slash.");
172:
173: File file = new File(bookInstanceDirectory, path);
174: if (!BookUtil.isWithin(bookInstanceDirectory, file))
175: throw new BookStoreException(
176: "It is not allowed to access a file outside of the book instance directory.");
177:
178: return file.renameTo(new File(file.getParent(), newName));
179: }
180:
181: private File getFileForStorage(String path) {
182: File file = new File(bookInstanceDirectory, path);
183: if (!BookUtil.isWithin(bookInstanceDirectory, file))
184: throw new BookStoreException(
185: "It is not allowed to write a file outside of the book instance directory.");
186:
187: File parentDir = file.getParentFile();
188: if (!parentDir.exists())
189: parentDir.mkdirs();
190:
191: return file;
192: }
193:
194: public boolean exists(String path) {
195: return getFile(path).exists();
196: }
197:
198: public long getLastModified(String path) {
199: return getFile(path).lastModified();
200: }
201:
202: public long getContentLength(String path) {
203: return getFile(path).length();
204: }
205:
206: private File getFile(String path) {
207: File file = new File(bookInstanceDirectory, path);
208: if (!BookUtil.isWithin(bookInstanceDirectory, file))
209: throw new BookStoreException(
210: "It is not allowed to access a file outside of the book instance directory.");
211: return file;
212: }
213:
214: boolean isLocked() {
215: File lockFile = new File(getDirectory(),
216: CommonBookInstance.LOCK_FILE_NAME);
217: return lockFile.exists();
218: }
219:
220: public void lock() {
221: throw new BookStoreException(
222: "This method should never be called.");
223: }
224:
225: public void unlock() {
226: throw new BookStoreException(
227: "This method should never be called.");
228: }
229:
230: public boolean canManage() {
231: throw new BookStoreException(
232: "This method should never be called.");
233: }
234:
235: public BookAcl getAcl() {
236: return aclResource.get();
237: }
238:
239: public void setAcl(BookAcl bookAcl) {
240: aclResource.store(bookAcl);
241: }
242:
243: public PublicationsInfo getPublicationsInfo() {
244: return publicationsInfoResource.get();
245: }
246:
247: public void addPublication(PublicationInfo publicationInfo) {
248: PublicationInfo[] infos = getPublicationsInfo().getInfos();
249: PublicationInfo[] newInfos = new PublicationInfo[infos.length + 1];
250: System.arraycopy(infos, 0, newInfos, 0, infos.length);
251: newInfos[newInfos.length - 1] = publicationInfo;
252: publicationsInfoResource.store(new PublicationsInfo(newInfos));
253: }
254:
255: public void setPublications(PublicationsInfo publicationsInfo) {
256: publicationsInfoResource.store(publicationsInfo);
257: }
258:
259: public BookInstanceMetaData getMetaData() {
260: return (BookInstanceMetaData) metaDataResource.get().clone();
261: }
262:
263: public void setMetaData(BookInstanceMetaData metaData) {
264: metaDataResource.store((BookInstanceMetaData) metaData.clone());
265: }
266:
267: public URI getResourceURI(String path) {
268: return getFile(path).toURI();
269: }
270:
271: public String[] getDescendantPaths(String path) {
272: File file = getFile(path);
273: if (!file.isDirectory()) {
274: throw new RuntimeException("path is not a directory: "
275: + path);
276: }
277:
278: List<String> result = new ArrayList<String>();
279: collectPaths(file.listFiles(), result, bookInstanceDirectory
280: .getAbsolutePath());
281: return result.toArray(new String[result.size()]);
282: }
283:
284: private void collectPaths(File[] files, Collection<String> result,
285: String refPath) {
286: for (File file : files) {
287: if (file.isDirectory()) {
288: collectPaths(file.listFiles(), result, refPath);
289: } else {
290: String path = file.getAbsolutePath();
291: if (!path.startsWith(refPath))
292: throw new RuntimeException(
293: "Assertion error: path does not start with refPath");
294: result.add(path.substring(refPath.length()));
295: }
296: }
297: }
298:
299: /**
300: * Method to be called upon initial creation of the book instance.
301: */
302: void initialize(String label, BookAcl initialAcl, long creator) {
303: BookInstanceMetaData metaData = new BookInstanceMetaData(label,
304: new Date(), creator);
305: // First write a lock file, before writing the other meta files, so that no other user can get a lock on the book instance
306: try {
307: new File(bookInstanceDirectory, LOCK_FILE_NAME)
308: .createNewFile();
309: } catch (IOException e) {
310: throw new BookStoreException(
311: "Error locking newly created book instance.", e);
312: }
313: metaDataResource.store(metaData);
314: publicationsInfoResource.store(new PublicationsInfo(
315: new PublicationInfo[0]));
316: aclResource.store(initialAcl);
317: }
318:
319: private static XmlOptions getMetaXmlOptions() {
320: XmlOptions xmlOptions = new XmlOptions();
321: xmlOptions.setSavePrettyPrint();
322: xmlOptions.setUseDefaultNamespace();
323: return xmlOptions;
324: }
325:
326: abstract class RefreshableResource {
327: private final File file;
328: private long lastModifed;
329: private Object object;
330: private final String what;
331:
332: public RefreshableResource(File file, String what) {
333: this .file = file;
334: this .what = what;
335: }
336:
337: protected Object getObject() {
338: if (object == null || (lastModifed != file.lastModified()))
339: loadObject();
340: return object;
341: }
342:
343: protected void loadObject() {
344: synchronized (file) {
345: long lastModified = file.lastModified();
346: Object object;
347: try {
348: object = create(new BufferedInputStream(
349: new FileInputStream(file)));
350: } catch (FileNotFoundException e) {
351: owner
352: .notifyBookInstanceDeleted(CommonBookInstance.this );
353: throw new NonExistingBookInstanceException(
354: getName());
355: } catch (Exception e) {
356: throw new BookStoreException("Error loading book "
357: + what, e);
358: }
359: this .object = object;
360: this .lastModifed = lastModified;
361: }
362: }
363:
364: protected abstract Object create(InputStream is)
365: throws Exception;
366:
367: protected void store(Object object, XmlObject xmlObject) {
368: synchronized (file) {
369: try {
370: OutputStream os = null;
371: try {
372: os = new BufferedOutputStream(
373: new FileOutputStream(file));
374: xmlObject.save(os, getMetaXmlOptions());
375: } finally {
376: if (os != null)
377: os.close();
378: }
379: } catch (Exception e) {
380: throw new BookStoreException(
381: "Error storing book ACL.", e);
382: }
383: this .object = object;
384: this .lastModifed = file.lastModified();
385: }
386: }
387: }
388:
389: class AclResource extends RefreshableResource {
390: public AclResource(File file) {
391: super (file, "ACL");
392: }
393:
394: protected Object create(InputStream is) throws Exception {
395: return BookAclBuilder.build(is);
396: }
397:
398: public BookAcl get() {
399: return (BookAcl) getObject();
400: }
401:
402: public void store(BookAcl bookAcl) {
403: store(bookAcl, bookAcl.getXml());
404: }
405: }
406:
407: class PublicationsInfoResource extends RefreshableResource {
408: public PublicationsInfoResource(File file) {
409: super (file, "publications info");
410: }
411:
412: protected Object create(InputStream is) throws Exception {
413: return PublicationsInfoBuilder.build(is);
414: }
415:
416: PublicationsInfo get() {
417: return (PublicationsInfo) getObject();
418: }
419:
420: void store(PublicationsInfo publicationsInfo) {
421: store(publicationsInfo, publicationsInfo.getXml());
422: }
423: }
424:
425: class MetaDataResource extends RefreshableResource {
426: public MetaDataResource(File file) {
427: super (file, "meta data");
428: }
429:
430: protected Object create(InputStream is) throws Exception {
431: return BookInstanceMetaDataBuilder.build(is);
432: }
433:
434: public BookInstanceMetaData get() {
435: return (BookInstanceMetaData) getObject();
436: }
437:
438: public void store(BookInstanceMetaData metaData) {
439: store(metaData, metaData.getXml());
440: }
441: }
442: }
|