1 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 ; 43 import java.util.*; 44 45 49 public class CommonBookStore implements ThreadSafe, Configurable, Initializable, Serviceable, Contextualizable, 50 ExtensionProvider, Disposable, LogEnabled { 51 private String storageDirectoryPath; 52 private File 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 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 { 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 ("Specified BookStore storageDirectory points to a non-existing location: " + source.getURI()); 87 if (!storageDirectory.isDirectory()) 88 throw new Exception ("Specified BookStore storageDirectory does not point to a directory: " + source.getURI()); 89 } else { 90 throw new Exception ("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 (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 e) { 119 } 121 } 122 } 123 124 public Object createExtension(Repository repository) { 125 return new BookStoreImpl(this, repository); 126 } 127 128 BookInstance createBookInstance(String name, String label, Repository repository) { 129 name = name.toLowerCase(); 130 checkValidBookInstanceName(name); 131 132 File 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 name, Repository repository) { 147 assureBookInstancesLoaded(); 148 CommonBookInstance commonBookInstance = (CommonBookInstance)bookInstances.get(name); 149 if (commonBookInstance == null) { 150 File 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 } 184 } 185 186 return result; 187 } 188 189 void deleteBookInstance(String name, Repository repository) { 190 UserBookInstance bookInstance = (UserBookInstance)getBookInstance(name, repository); 191 bookInstance.lock(); 192 boolean fileDeleted = false; 193 try { 194 File directory = bookInstance.getDirectory(); 195 196 for (int i = 0; i < CommonBookInstance.META_FILES.length; i++) { 198 deleteFile(new File (directory, CommonBookInstance.META_FILES[i])); 199 fileDeleted = true; 200 } 201 202 deleteRecursive(directory); 203 directory.delete(); 204 } catch (Throwable e) { 205 throw new BookStoreException("Error while deleting book instance \"" + name + "\".", e); 206 } finally { 207 if (!fileDeleted) { 208 bookInstance.unlock(); 210 } 211 } 212 bookInstances.remove(name); 213 } 214 215 private void deleteRecursive(File file) { 216 File [] 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 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 oldName, String newName, Repository repository) { 231 checkValidBookInstanceName(newName); 232 UserBookInstance bookInstance = (UserBookInstance)getBookInstance(oldName, repository); 233 bookInstance.lock(); 234 File oldDir = bookInstance.getDirectory(); 235 File newDir = new File (oldDir.getParent(), newName); 236 if (newDir.exists()) { 237 bookInstance.unlock(); 238 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 lockFile = new File (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 name, Repository repository) { 254 return new File (storageDirectory, name.toLowerCase()).exists(); 255 } 256 257 private File getBookInstanceDir(String name) { 258 File bookInstanceDir = new File (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 267 private void checkValidBookInstanceName(String name) { 268 String error = BookStoreUtil.isValidBookInstanceName(name); 269 if (error != null) 270 throw new BookStoreException(error); 271 } 272 273 276 private synchronized void assureBookInstancesLoaded() { 277 if (bookInstancesLoaded) 278 return; 279 280 HashMap bookInstances = new HashMap(); 281 File [] 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 file) { 295 for (int j = 0; j < CommonBookInstance.META_FILES.length; j++) { 297 File metaFile = new File (file, CommonBookInstance.META_FILES[j]); 298 if (!metaFile.exists()) { 299 return false; 300 } 301 } 302 return true; 303 } 304 305 308 void notifyBookInstanceDeleted(CommonBookInstance bookInstance) { 309 bookInstances.remove(bookInstance.getName()); 310 } 311 312 class NewBookDetector implements Runnable { 313 public void run() { 314 while (true) { 317 try { 318 Thread.sleep(checkChangesInterval); 319 if (bookInstancesLoaded) { 320 File [] files = storageDirectory.listFiles(); 321 for (int i = 0; i < files.length; i++) { 322 String 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 e) { 329 if (e instanceof InterruptedException ) { 330 return; 332 } else { 333 logger.error("[Daisy books] Error in new book detection thread.", e); 334 } 335 } 336 } 337 } 338 } 339 } 340 | Popular Tags |