KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > books > store > impl > CommonBookStore


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.outerj.daisy.books.store.impl;
17
18 import org.apache.avalon.framework.service.ServiceManager;
19 import org.apache.avalon.framework.service.ServiceException;
20 import org.apache.avalon.framework.service.Serviceable;
21 import org.apache.avalon.framework.configuration.Configuration;
22 import org.apache.avalon.framework.configuration.ConfigurationException;
23 import org.apache.avalon.framework.configuration.Configurable;
24 import org.apache.avalon.framework.thread.ThreadSafe;
25 import org.apache.avalon.framework.activity.Initializable;
26 import org.apache.avalon.framework.activity.Disposable;
27 import org.apache.avalon.framework.logger.LogEnabled;
28 import org.apache.avalon.framework.logger.Logger;
29 import org.apache.avalon.framework.context.Contextualizable;
30 import org.apache.avalon.framework.context.Context;
31 import org.apache.avalon.framework.context.ContextException;
32 import org.apache.excalibur.source.SourceResolver;
33 import org.apache.excalibur.source.Source;
34 import org.apache.excalibur.source.impl.FileSource;
35 import org.outerj.daisy.books.store.*;
36 import org.outerj.daisy.configutil.PropertyResolver;
37 import org.outerj.daisy.repository.ExtensionProvider;
38 import org.outerj.daisy.repository.Repository;
39 import org.outerj.daisy.repository.ExtensionRegistrar;
40 import org.outerj.daisy.frontend.util.WikiDataDirHelper;
41
42 import java.io.File JavaDoc;
43 import java.util.*;
44
45 /**
46  * The book store. This class should be a singleton. It registers an extension with the Daisy repository manager.
47  * Users should access the book store through the repository, i.e. repository.getExtension("BookStore").
48  */

49 public class CommonBookStore implements ThreadSafe, Configurable, Initializable, Serviceable, Contextualizable,
50         ExtensionProvider, Disposable, LogEnabled {
51     private String JavaDoc storageDirectoryPath;
52     private File JavaDoc storageDirectory;
53     private ServiceManager serviceManager;
54     private Map bookInstances = Collections.synchronizedMap(new HashMap());
55     private boolean bookInstancesLoaded = false;
56     private long checkChangesInterval;
57     private Thread JavaDoc checkChangesThread;
58     private Logger logger;
59     private Context context;
60
61     public void enableLogging(Logger logger) {
62         this.logger = logger;
63     }
64
65     public void contextualize(Context context) throws ContextException {
66         this.context = context;
67     }
68
69     public void configure(Configuration configuration) throws ConfigurationException {
70         storageDirectoryPath = PropertyResolver.resolveProperties(configuration.getChild("storageDirectory").getValue(), WikiDataDirHelper.getResolveProperties(context));
71         checkChangesInterval = configuration.getChild("checkChangesInterval").getValueAsLong(60) * 1000;
72     }
73
74     public void service(ServiceManager serviceManager) throws ServiceException {
75         this.serviceManager = serviceManager;
76     }
77
78     public void initialize() throws Exception JavaDoc {
79         SourceResolver sourceResolver = (SourceResolver)serviceManager.lookup(SourceResolver.ROLE);
80         Source source = null;
81         try {
82             source = sourceResolver.resolveURI(storageDirectoryPath);
83             if (source instanceof FileSource) {
84                 storageDirectory = ((FileSource)source).getFile();
85                 if (!storageDirectory.exists())
86                     throw new Exception JavaDoc("Specified BookStore storageDirectory points to a non-existing location: " + source.getURI());
87                 if (!storageDirectory.isDirectory())
88                     throw new Exception JavaDoc("Specified BookStore storageDirectory does not point to a directory: " + source.getURI());
89             } else {
90                 throw new Exception JavaDoc("Specified BookStore storageDirectory does not point to a filesystem location: " + source.getURI());
91             }
92         } finally {
93             if (source != null)
94                 sourceResolver.release(source);
95             if (sourceResolver != null)
96                 serviceManager.release(sourceResolver);
97         }
98
99         ExtensionRegistrar extensionRegistrar = null;
100         try {
101             extensionRegistrar = (ExtensionRegistrar)serviceManager.lookup("daisy-repository-manager");
102             extensionRegistrar.registerExtension("BookStore", this);
103         } finally {
104             serviceManager.release(extensionRegistrar);
105         }
106
107         checkChangesThread = new Thread JavaDoc(new NewBookDetector());
108         checkChangesThread.setName("Book Store Change Detection Thread");
109         checkChangesThread.setDaemon(true);
110         checkChangesThread.start();
111     }
112
113     public void dispose() {
114         if (checkChangesThread != null) {
115             checkChangesThread.interrupt();
116             try {
117                 checkChangesThread.join(5);
118             } catch (InterruptedException JavaDoc e) {
119                 // ignore
120
}
121         }
122     }
123
124     public Object JavaDoc createExtension(Repository repository) {
125         return new BookStoreImpl(this, repository);
126     }
127
128     BookInstance createBookInstance(String JavaDoc name, String JavaDoc label, Repository repository) {
129         name = name.toLowerCase();
130         checkValidBookInstanceName(name);
131
132         File JavaDoc bookInstanceDir = getBookInstanceDir(name);
133
134         if (!bookInstanceDir.mkdir())
135             throw new BookStoreException("Could not create the book instance directory, probably a book instance named \"" + name + "\" already exists, or the name contains illegal characters, or there is no write access to the bookstore directory, or something similar.");
136
137         BookAcl bookAcl = new BookAcl(new BookAclEntry[] { new BookAclEntry(BookAclSubjectType.EVERYONE, -1, BookAclActionType.GRANT, BookAclActionType.GRANT)});
138
139         CommonBookInstance commonBookInstance = new CommonBookInstance(bookInstanceDir, this);
140         commonBookInstance.initialize(label, bookAcl, repository.getUserId());
141         bookInstances.put(bookInstanceDir.getName(), commonBookInstance);
142         UserBookInstance userBookInstance = new UserBookInstance(commonBookInstance, repository, true);
143         return userBookInstance;
144     }
145
146     BookInstance getBookInstance(String JavaDoc name, Repository repository) {
147         assureBookInstancesLoaded();
148         CommonBookInstance commonBookInstance = (CommonBookInstance)bookInstances.get(name);
149         if (commonBookInstance == null) {
150             File JavaDoc bookInstanceDir = getBookInstanceDir(name);
151             if (isBookInstanceDir(bookInstanceDir)) {
152                 commonBookInstance = new CommonBookInstance(bookInstanceDir, this);
153                 bookInstances.put(bookInstanceDir.getName(), commonBookInstance);
154             } else {
155                 throw new NonExistingBookInstanceException(name);
156             }
157         }
158
159         AclResult aclResult = BookAclEvaluator.evaluate(commonBookInstance.getAcl(), repository.getUserId(), repository.getActiveRoleIds());
160         if (!aclResult.canRead())
161             throw new BookStoreAccessDeniedException();
162
163         UserBookInstance userBookInstance = new UserBookInstance(commonBookInstance, repository, false);
164         return userBookInstance;
165     }
166
167     Collection getBookInstances(Repository repository) {
168         assureBookInstancesLoaded();
169         CommonBookInstance[] bookInstances = (CommonBookInstance[])this.bookInstances.values().toArray(new CommonBookInstance[0]);
170
171         long userId = repository.getUserId();
172         long[] activeRoleIds = repository.getActiveRoleIds();
173
174         List result = new ArrayList();
175         for (int i = 0; i < bookInstances.length; i++) {
176             try {
177                 AclResult aclResult = BookAclEvaluator.evaluate(bookInstances[i].getAcl(), userId, activeRoleIds);
178                 if (aclResult.canRead() && !bookInstances[i].isLocked()) {
179                     result.add(new UserBookInstance(bookInstances[i], repository, false));
180                 }
181             } catch (NonExistingBookInstanceException e) {
182                 // book instance got deleted while we were doing this, simply skip it
183
}
184         }
185
186         return result;
187     }
188
189     void deleteBookInstance(String JavaDoc name, Repository repository) {
190         UserBookInstance bookInstance = (UserBookInstance)getBookInstance(name, repository);
191         bookInstance.lock();
192         boolean fileDeleted = false;
193         try {
194             File JavaDoc directory = bookInstance.getDirectory();
195
196             // first delete the meta files, so that reading from this book instance will fail as fast as possible
197
for (int i = 0; i < CommonBookInstance.META_FILES.length; i++) {
198                 deleteFile(new File JavaDoc(directory, CommonBookInstance.META_FILES[i]));
199                 fileDeleted = true;
200             }
201
202             deleteRecursive(directory);
203             directory.delete();
204         } catch (Throwable JavaDoc e) {
205             throw new BookStoreException("Error while deleting book instance \"" + name + "\".", e);
206         } finally {
207             if (!fileDeleted) {
208                 // no files were removed, thus the book instance still exists
209
bookInstance.unlock();
210             }
211         }
212         bookInstances.remove(name);
213     }
214
215     private void deleteRecursive(File JavaDoc file) {
216         File JavaDoc[] children = file.listFiles();
217         for (int i = 0; i < children.length; i++) {
218             if (children[i].isDirectory()) {
219                 deleteRecursive(children[i]);
220             }
221             deleteFile(children[i]);
222         }
223     }
224
225     private void deleteFile(File JavaDoc file) {
226         if (!file.delete())
227             throw new BookStoreException("Error deleting book instance: could not delete the following file: " + file.getName());
228     }
229
230     public void renameBookInstance(String JavaDoc oldName, String JavaDoc newName, Repository repository) {
231         checkValidBookInstanceName(newName);
232         UserBookInstance bookInstance = (UserBookInstance)getBookInstance(oldName, repository);
233         bookInstance.lock();
234         File JavaDoc oldDir = bookInstance.getDirectory();
235         File JavaDoc newDir = new File JavaDoc(oldDir.getParent(), newName);
236         if (newDir.exists()) {
237             bookInstance.unlock();
238             // Note: in the exception, we do not explicitely tell whether it exists or not, as the user
239
// might not have access to the other book instance
240
throw new BookStoreException("Renamed failed, there might be another book instance named \"" + newName + "\".");
241         }
242         boolean success = oldDir.renameTo(newDir);
243         if (!success) {
244             bookInstance.unlock();
245             throw new BookStoreException("Renamed failed, there might be another book instance named \"" + newName + "\".");
246         }
247
248         File JavaDoc lockFile = new File JavaDoc(newDir, CommonBookInstance.LOCK_FILE_NAME);
249         lockFile.delete();
250         bookInstances.put(newDir.getName(), new CommonBookInstance(newDir, CommonBookStore.this));
251     }
252
253     public boolean existsBookInstance(String JavaDoc name, Repository repository) {
254         return new File JavaDoc(storageDirectory, name.toLowerCase()).exists();
255     }
256
257     private File JavaDoc getBookInstanceDir(String JavaDoc name) {
258         File JavaDoc bookInstanceDir = new File JavaDoc(storageDirectory, name);
259         if (!BookUtil.isWithin(storageDirectory, bookInstanceDir))
260             throw new BookStoreException("Tried to access a directory outside the book store.");
261         return bookInstanceDir;
262     }
263
264     /**
265      * Assumes the name has been lower cased.
266      */

267     private void checkValidBookInstanceName(String JavaDoc name) {
268         String JavaDoc error = BookStoreUtil.isValidBookInstanceName(name);
269         if (error != null)
270             throw new BookStoreException(error);
271     }
272
273     /**
274      * Does the initial loading of the book instances on first need.
275      */

276     private synchronized void assureBookInstancesLoaded() {
277         if (bookInstancesLoaded)
278             return;
279
280         HashMap bookInstances = new HashMap();
281         File JavaDoc[] files = storageDirectory.listFiles();
282         for (int i = 0; i < files.length; i++) {
283             if (files[i].isDirectory()) {
284                 if (isBookInstanceDir(files[i])) {
285                     bookInstances.put(files[i].getName(), new CommonBookInstance(files[i], this));
286                 }
287             }
288         }
289
290         this.bookInstances = bookInstances;
291         this.bookInstancesLoaded = true;
292     }
293
294     private boolean isBookInstanceDir(File JavaDoc file) {
295         // When is a directory a valid book instance directory? If it contains all required meta files.
296
for (int j = 0; j < CommonBookInstance.META_FILES.length; j++) {
297             File JavaDoc metaFile = new File JavaDoc(file, CommonBookInstance.META_FILES[j]);
298             if (!metaFile.exists()) {
299                 return false;
300             }
301         }
302         return true;
303     }
304
305     /**
306      * Called by a {@link CommonBookInstance} when it detects it has been deleted.
307      */

308     void notifyBookInstanceDeleted(CommonBookInstance bookInstance) {
309         bookInstances.remove(bookInstance.getName());
310     }
311
312     class NewBookDetector implements Runnable JavaDoc {
313         public void run() {
314             // Note: this thread only needs to detect new books, deleted books are automatically
315
// detected when accessing them (see also notifyBookInstanceDeleted())
316
while (true) {
317                 try {
318                     Thread.sleep(checkChangesInterval);
319                     if (bookInstancesLoaded) {
320                         File JavaDoc[] files = storageDirectory.listFiles();
321                         for (int i = 0; i < files.length; i++) {
322                             String JavaDoc name = files[i].getName();
323                             if (!bookInstances.containsKey(name) && isBookInstanceDir(files[i])) {
324                                 bookInstances.put(name, new CommonBookInstance(files[i], CommonBookStore.this));
325                             }
326                         }
327                     }
328                 } catch (Throwable JavaDoc e) {
329                     if (e instanceof InterruptedException JavaDoc) {
330                         // time to stop working
331
return;
332                     } else {
333                         logger.error("[Daisy books] Error in new book detection thread.", e);
334                     }
335                 }
336             }
337         }
338     }
339 }
340
Popular Tags