001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /* $Id: FileItemManager.java 485769 2006-12-11 17:41:23Z andreas $ */
020:
021: package org.apache.lenya.ac.file;
022:
023: import java.io.File;
024: import java.io.FileFilter;
025: import java.io.IOException;
026: import java.lang.reflect.Constructor;
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import org.apache.avalon.framework.configuration.Configuration;
036: import org.apache.avalon.framework.configuration.ConfigurationException;
037: import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
038: import org.apache.avalon.framework.logger.AbstractLogEnabled;
039: import org.apache.avalon.framework.logger.Logger;
040: import org.apache.lenya.ac.AccessControlException;
041: import org.apache.lenya.ac.AccreditableManager;
042: import org.apache.lenya.ac.Group;
043: import org.apache.lenya.ac.Groupable;
044: import org.apache.lenya.ac.Item;
045: import org.apache.lenya.ac.ItemManager;
046: import org.apache.lenya.ac.ItemManagerListener;
047: import org.apache.lenya.ac.impl.ItemConfiguration;
048:
049: /**
050: * Abstract superclass for classes that manage items loaded from configuration
051: * files.
052: */
053: public abstract class FileItemManager extends AbstractLogEnabled
054: implements ItemManager {
055:
056: private Map items = new HashMap();
057: private File configurationDirectory;
058: private DirectoryChangeNotifier notifier;
059:
060: private AccreditableManager accreditableManager;
061:
062: /**
063: * Create a new ItemManager.
064: * @param accreditableManager The {@link AccreditableManager}.
065: */
066: protected FileItemManager(AccreditableManager accreditableManager) {
067: this .accreditableManager = accreditableManager;
068: }
069:
070: /**
071: * Configures the item manager.
072: * @param _configurationDirectory where the items are fetched from
073: * @throws AccessControlException if the item manager cannot be instantiated
074: */
075: public void configure(File _configurationDirectory)
076: throws AccessControlException {
077: assert _configurationDirectory != null;
078:
079: if (!_configurationDirectory.exists()
080: || !_configurationDirectory.isDirectory()) {
081: throw new AccessControlException("The directory ["
082: + _configurationDirectory.getAbsolutePath()
083: + "] does not exist!");
084: }
085:
086: this .configurationDirectory = _configurationDirectory;
087: this .notifier = new DirectoryChangeNotifier(
088: _configurationDirectory, getFileFilter());
089: this .notifier.enableLogging(getLogger());
090: loadItems();
091: }
092:
093: /**
094: * Reloads the items if an item was changed / added / removed.
095: * @throws AccessControlException when something went wrong.
096: */
097: protected void loadItems() throws AccessControlException {
098:
099: boolean changed;
100: try {
101: changed = this .notifier.hasChanged();
102: } catch (IOException e) {
103: throw new AccessControlException(e);
104: }
105:
106: if (changed) {
107:
108: if (getLogger().isDebugEnabled()) {
109: getLogger().debug(
110: "Item configuration has changed - reloading.");
111: }
112:
113: File[] addedFiles = this .notifier.getAddedFiles();
114:
115: for (int i = 0; i < addedFiles.length; i++) {
116: Item item = loadItem(addedFiles[i]);
117: add(item);
118: }
119:
120: File[] removedFiles = this .notifier.getRemovedFiles();
121: for (int i = 0; i < removedFiles.length; i++) {
122: String fileName = removedFiles[i].getName();
123: String id = fileName.substring(0, fileName.length()
124: - getSuffix().length());
125:
126: Item item = (Item) this .items.get(id);
127:
128: if (item != null) {
129:
130: if (item instanceof Groupable) {
131: ((Groupable) item).removeFromAllGroups();
132: }
133: if (item instanceof Group) {
134: ((Group) item).removeAllMembers();
135: }
136:
137: remove(item);
138: }
139: }
140:
141: File[] changedFiles = this .notifier.getChangedFiles();
142: for (int i = 0; i < changedFiles.length; i++) {
143: Item item = loadItem(changedFiles[i]);
144: update(item);
145: }
146:
147: }
148:
149: }
150:
151: /**
152: * Loads an item from a file.
153: * @param file The file.
154: * @return An item.
155: * @throws AccessControlException when something went wrong.
156: */
157: protected Item loadItem(File file) throws AccessControlException {
158: Configuration config = getItemConfiguration(file);
159:
160: String fileName = file.getName();
161: String id = fileName.substring(0, fileName.length()
162: - getSuffix().length());
163: Item item = (Item) this .items.get(id);
164:
165: String klass = ItemConfiguration.getItemClass(config);
166: if (item == null) {
167: try {
168: Class[] paramTypes = { ItemManager.class, Logger.class };
169: Constructor ctor = Class.forName(klass).getConstructor(
170: paramTypes);
171: Object[] params = { this , getLogger() };
172: item = (Item) ctor.newInstance(params);
173: } catch (Exception e) {
174: String errorMsg = "Exception when trying to instanciate: "
175: + klass
176: + " with exception: "
177: + e.fillInStackTrace();
178:
179: // an exception occured when trying to instanciate
180: // a user.
181: getLogger().error(errorMsg);
182: throw new AccessControlException(errorMsg, e);
183: }
184: }
185:
186: try {
187: item.configure(config);
188: } catch (ConfigurationException e) {
189: String errorMsg = "Exception when trying to configure: "
190: + klass;
191: throw new AccessControlException(errorMsg, e);
192: }
193: return item;
194: }
195:
196: /**
197: * Loads teh configuration of an item from a file.
198: * @param file The file.
199: * @return A configuration.
200: * @throws AccessControlException when something went wrong.
201: */
202: protected Configuration getItemConfiguration(File file)
203: throws AccessControlException {
204: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
205: Configuration config = null;
206:
207: try {
208: assert file.exists();
209: config = builder.buildFromFile(file);
210: } catch (Exception e) {
211: String errorMsg = "Exception when reading the configuration from file: "
212: + file.getName();
213:
214: // an exception occured when trying to read the configuration
215: // from the identity file.
216: getLogger().error(errorMsg);
217: throw new AccessControlException(errorMsg, e);
218: }
219: return config;
220: }
221:
222: protected void removeItem(File file) {
223: // do nothing
224: }
225:
226: /**
227: * Returns an item for a given ID.
228: * @param id The id.
229: * @return An item.
230: */
231: public Item getItem(String id) {
232: try {
233: loadItems();
234: } catch (AccessControlException e) {
235: throw new IllegalStateException(e.getMessage());
236: }
237: return (Item) this .items.get(id);
238: }
239:
240: /**
241: * get all items
242: * @return an array of items
243: */
244: public Item[] getItems() {
245: try {
246: loadItems();
247: } catch (AccessControlException e) {
248: throw new IllegalStateException(e.getMessage());
249: }
250: return (Item[]) this .items.values().toArray(
251: new Item[this .items.values().size()]);
252: }
253:
254: /**
255: * Add an Item to this manager
256: * @param item to be added
257: * @throws AccessControlException when the notification threw this
258: * exception.
259: */
260: public void add(Item item) throws AccessControlException {
261: assert item != null;
262: this .items.put(item.getId(), item);
263: if (getLogger().isDebugEnabled()) {
264: getLogger().debug("Item [" + item + "] added.");
265: }
266: notifyAdded(item);
267: }
268:
269: /**
270: * Remove an item from this manager
271: * @param item to be removed
272: * @throws AccessControlException when the notification threw this
273: * exception.
274: */
275: public void remove(Item item) throws AccessControlException {
276: this .items.remove(item.getId());
277: if (getLogger().isDebugEnabled()) {
278: getLogger().debug("Item [" + item + "] removed.");
279: }
280: notifyRemoved(item);
281: }
282:
283: /**
284: * Update an item.
285: * @param newItem The new version of the item.
286: * @throws AccessControlException when the notification threw this
287: * exception.
288: */
289: public void update(Item newItem) throws AccessControlException {
290: this .items.remove(newItem.getId());
291: this .items.put(newItem.getId(), newItem);
292: if (getLogger().isDebugEnabled()) {
293: getLogger().debug("Item [" + newItem + "] updated.");
294: }
295: }
296:
297: /**
298: * Returns if the ItemManager contains an object.
299: * @param item The object.
300: * @return A boolean value.
301: */
302: public boolean contains(Item item) {
303: try {
304: loadItems();
305: } catch (AccessControlException e) {
306: throw new IllegalStateException(e.getMessage());
307: }
308: return this .items.containsValue(item);
309: }
310:
311: /**
312: * Get the directory where the items are located.
313: * @return a <code>File</code>
314: */
315: public File getConfigurationDirectory() {
316: return this .configurationDirectory;
317: }
318:
319: /**
320: * Get a file filter which filters for files containing items.
321: * @return a <code>FileFilter</code>
322: */
323: protected FileFilter getFileFilter() {
324: FileFilter filter = new FileFilter() {
325: public boolean accept(File pathname) {
326: return (pathname.getName().endsWith(getSuffix()));
327: }
328: };
329:
330: return filter;
331: }
332:
333: /**
334: * Returns the file extension to be used.
335: * @return A string.
336: */
337: protected abstract String getSuffix();
338:
339: private List itemManagerListeners = new ArrayList();
340:
341: /**
342: * Attaches an item manager listener to this item manager.
343: * @param listener An item manager listener.
344: */
345: public void addItemManagerListener(ItemManagerListener listener) {
346: if (getLogger().isDebugEnabled()) {
347: getLogger().debug("Adding listener: [" + listener + "]");
348: }
349: if (!this .itemManagerListeners.contains(listener)) {
350: this .itemManagerListeners.add(listener);
351: }
352: }
353:
354: /**
355: * Removes an item manager listener from this item manager.
356: * @param listener An item manager listener.
357: */
358: public void removeItemManagerListener(ItemManagerListener listener) {
359: if (getLogger().isDebugEnabled()) {
360: getLogger().debug("Removing listener: [" + listener + "]");
361: }
362: this .itemManagerListeners.remove(listener);
363: }
364:
365: /**
366: * Notifies the listeners that an item was added.
367: * @param item The item that was added.
368: * @throws AccessControlException if an error occurs.
369: */
370: protected void notifyAdded(Item item) throws AccessControlException {
371: if (getLogger().isDebugEnabled()) {
372: getLogger().debug("Item was added: [" + item + "]");
373: }
374: List clone = new ArrayList(this .itemManagerListeners);
375: for (Iterator i = clone.iterator(); i.hasNext();) {
376: ItemManagerListener listener = (ItemManagerListener) i
377: .next();
378: listener.itemAdded(item);
379: }
380: }
381:
382: /**
383: * Notifies the listeners that an item was removed.
384: * @param item The item that was removed.
385: * @throws AccessControlException if an error occurs.
386: */
387: protected void notifyRemoved(Item item)
388: throws AccessControlException {
389: if (getLogger().isDebugEnabled()) {
390: getLogger().debug("Item was removed: [" + item + "]");
391: }
392: List clone = new ArrayList(this .itemManagerListeners);
393: for (Iterator i = clone.iterator(); i.hasNext();) {
394: ItemManagerListener listener = (ItemManagerListener) i
395: .next();
396: if (getLogger().isDebugEnabled()) {
397: getLogger().debug(
398: "Notifying listener: [" + listener + "]");
399: }
400: listener.itemRemoved(item);
401: }
402: }
403:
404: /**
405: * Helper class to observe a directory for changes.
406: */
407: public static class DirectoryChangeNotifier extends
408: AbstractLogEnabled {
409:
410: /**
411: * Ctor.
412: * @param _directory The directory to observe.
413: * @param _filter A filter to specify the file type to observe.
414: */
415: public DirectoryChangeNotifier(File _directory,
416: FileFilter _filter) {
417: this .directory = _directory;
418: this .filter = _filter;
419: }
420:
421: private File directory;
422: private FileFilter filter;
423: private Map canonicalPath2LastModified = new HashMap();
424:
425: private Set addedFiles = new HashSet();
426: private Set removedFiles = new HashSet();
427: private Set changedFiles = new HashSet();
428:
429: /**
430: * Checks if the directory has changed (a new file was added, a file was
431: * removed, a file has changed).
432: * @return A boolean value.
433: * @throws IOException when something went wrong.
434: */
435: public boolean hasChanged() throws IOException {
436:
437: this .addedFiles.clear();
438: this .removedFiles.clear();
439: this .changedFiles.clear();
440:
441: File[] files = this .directory.listFiles(this .filter);
442:
443: Set newPathSet = new HashSet();
444:
445: for (int i = 0; i < files.length; i++) {
446: String canonicalPath = files[i].getCanonicalPath();
447: newPathSet.add(canonicalPath);
448:
449: if (!this .canonicalPath2LastModified
450: .containsKey(canonicalPath)) {
451: this .addedFiles.add(new File(canonicalPath));
452:
453: if (getLogger().isDebugEnabled()) {
454: getLogger().debug(
455: "New file: [" + canonicalPath + "]");
456: }
457:
458: } else {
459: Long lastModifiedObject = (Long) this .canonicalPath2LastModified
460: .get(canonicalPath);
461: long lastModified = lastModifiedObject.longValue();
462: if (lastModified < files[i].lastModified()) {
463: this .changedFiles.add(files[i]);
464: if (getLogger().isDebugEnabled()) {
465: getLogger().debug(
466: "File has changed: ["
467: + canonicalPath + "]");
468: }
469: }
470: }
471: Long lastModified = new Long(files[i].lastModified());
472: this .canonicalPath2LastModified.put(canonicalPath,
473: lastModified);
474: }
475:
476: Set oldPathSet = this .canonicalPath2LastModified.keySet();
477: String[] oldPaths = (String[]) oldPathSet
478: .toArray(new String[oldPathSet.size()]);
479: for (int i = 0; i < oldPaths.length; i++) {
480: if (!newPathSet.contains(oldPaths[i])) {
481: this .removedFiles.add(new File(oldPaths[i]));
482: this .canonicalPath2LastModified.remove(oldPaths[i]);
483: if (getLogger().isDebugEnabled()) {
484: getLogger().debug(
485: "File removed: [" + oldPaths[i] + "]");
486: }
487: }
488: }
489:
490: return !this .addedFiles.isEmpty()
491: || !this .removedFiles.isEmpty()
492: || !this .changedFiles.isEmpty();
493: }
494:
495: /**
496: * Returns the added files.
497: * @return An array of files.
498: */
499: public File[] getAddedFiles() {
500: return (File[]) this .addedFiles
501: .toArray(new File[this .addedFiles.size()]);
502: }
503:
504: /**
505: * Returns the removed files.
506: * @return An array of files.
507: */
508: public File[] getRemovedFiles() {
509: return (File[]) this .removedFiles
510: .toArray(new File[this .removedFiles.size()]);
511: }
512:
513: /**
514: * Returns the changed files.
515: * @return An array of files.
516: */
517: public File[] getChangedFiles() {
518: return (File[]) this .changedFiles
519: .toArray(new File[this .changedFiles.size()]);
520: }
521:
522: }
523:
524: public AccreditableManager getAccreditableManager() {
525: return this.accreditableManager;
526: }
527:
528: }
|