changeset 1175:ce3105f6910a

made metadata implementation pluggable. interfaces DirMeta and FileMeta instantiated by MetaFactory. current implementation reads index.meta files.
author robcast
date Wed, 03 Apr 2013 21:07:57 +0200
parents 4a0dd02a61b8
children 5f7fd411823c
files common/src/main/java/digilib/conf/DigilibConfiguration.java common/src/main/java/digilib/io/Directory.java common/src/main/java/digilib/io/DocuDirCache.java common/src/main/java/digilib/io/DocuDirectory.java common/src/main/java/digilib/io/DocuDirent.java common/src/main/java/digilib/io/DocuDirentImpl.java common/src/main/java/digilib/io/ImageFileSet.java common/src/main/java/digilib/meta/DirMeta.java common/src/main/java/digilib/meta/FileMeta.java common/src/main/java/digilib/meta/IndexMetaAuthLoader.java common/src/main/java/digilib/meta/IndexMetaDirMeta.java common/src/main/java/digilib/meta/IndexMetaFileMeta.java common/src/main/java/digilib/meta/MetaFactory.java servlet/src/main/java/digilib/conf/DigilibServletConfiguration.java servlet3/src/main/java/digilib/servlet/Scaler.java webapp/src/main/webapp/sample-images/p0005.jpg.meta
diffstat 16 files changed, 665 insertions(+), 388 deletions(-) [+]
line wrap: on
line diff
--- a/common/src/main/java/digilib/conf/DigilibConfiguration.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/conf/DigilibConfiguration.java	Wed Apr 03 21:07:57 2013 +0200
@@ -69,20 +69,20 @@
         initParams();
     }
 
-    /** Definition of parameters and default values.
-	 * 
-	 */
+    /**
+     * Definition of parameters and default values.
+     * 
+     */
     @SuppressWarnings("unchecked")
     protected void initParams() {
         /*
          * Definition of parameters and default values. System parameters that
          * are not read from config file have a type 's'.
          */
-        // digilib servlet version
-        newParameter("digilib.version", "2.0b1", null, 's');
+        // digilib version
+        newParameter("digilib.version", "2.1.3", null, 's');
         // DocuImage class instance
-        newParameter("servlet.docuimage.class",
-                digilib.image.ImageLoaderDocuImage.class, null, 's');
+        newParameter("servlet.docuimage.class", digilib.image.ImageLoaderDocuImage.class, null, 's');
         // sending image files as-is allowed
         newParameter("sendfile-allowed", Boolean.TRUE, null, 'f');
         // Type of DocuImage instance
@@ -129,8 +129,7 @@
      * @return
      * @throws IOException
      */
-    public static ImageInput identifyDocuImage(ImageInput imgf)
-            throws IOException {
+    public static ImageInput identifyDocuImage(ImageInput imgf) throws IOException {
         // use fresh DocuImage instance
         DocuImage di = getDocuImageInstance();
         return di.identify(imgf);
--- a/common/src/main/java/digilib/io/Directory.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/Directory.java	Wed Apr 03 21:07:57 2013 +0200
@@ -116,14 +116,14 @@
 	/**
 	 * @return
 	 */
-	Directory getParent() {
+	public Directory getParent() {
 		return parent;
 	}
 
 	/**
 	 * @param parent
 	 */
-	void setParent(Directory parent) {
+	public void setParent(Directory parent) {
 		this.parent = parent;
 	}
 
--- a/common/src/main/java/digilib/io/DocuDirCache.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/DocuDirCache.java	Wed Apr 03 21:07:57 2013 +0200
@@ -45,16 +45,16 @@
 public class DocuDirCache {
 
 	/** general logger for this class */
-	Logger logger = Logger.getLogger(this.getClass());
+	protected static Logger logger = Logger.getLogger(DocuDirCache.class);
 
 	/** HashMap of directories */
-	ConcurrentMap<String, DocuDirectory> map = new ConcurrentHashMap<String, DocuDirectory>();
+	protected ConcurrentMap<String, DocuDirectory> map = new ConcurrentHashMap<String, DocuDirectory>();
 
 	/** names of base directories */
-	String[] baseDirNames = null;
+	protected String[] baseDirNames = null;
 
 	/** array of allowed file classes (image/text) */
-	private FileClass[] fileClasses = null;
+	protected FileClass[] fileClasses = null;
 
 	/** number of files in the whole cache (approximate) */
 	protected AtomicInteger numImgFiles = new AtomicInteger(0);
--- a/common/src/main/java/digilib/io/DocuDirectory.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/DocuDirectory.java	Wed Apr 03 21:07:57 2013 +0200
@@ -28,18 +28,13 @@
  */
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-
-import org.xml.sax.SAXException;
 
 import digilib.io.FileOps.FileClass;
-import digilib.meta.IndexMetaAuthLoader;
-import digilib.meta.IndexMetaLoader;
-import digilib.meta.MetadataMap;
+import digilib.meta.DirMeta;
+import digilib.meta.MetaFactory;
 
 /**
  * @author casties
@@ -62,13 +57,7 @@
 	private Directory[] dirs = null;
 
 	/** directory metadata */
-	private MetadataMap dirMeta = null;
-
-	/** state of metadata is valid */
-	private boolean metaChecked = false;
-
-	/** unresolved file metadata */
-	private Map<String, MetadataMap> unresolvedFileMeta = null;
+	protected DirMeta meta = null;
 
 	/** time of last access of this object (not the filesystem) */
 	private long objectATime = 0;
@@ -104,6 +93,7 @@
 		// the first directory has to exist
 		dir = new File(baseDirName, path);
 		isValid = dir.isDirectory();
+		meta = MetaFactory.getDirMetaInstance();
 	}
 
 	/**
@@ -264,96 +254,18 @@
 	 *  
 	 */
 	public void readMeta() {
-		// check for directory metadata...
-		File mf = new File(dir, "index.meta");
-		if (mf.canRead()) {
-			IndexMetaAuthLoader ml = new IndexMetaAuthLoader();
-			try {
-				// read directory meta file
-				Map<String, MetadataMap> fileMeta = ml.loadUri(mf.toURI());
-				if (fileMeta == null) {
-					return;
-				}
-				// meta for the directory itself is in the "" bin
-				dirMeta = fileMeta.remove("");
-				// read meta for files in this directory
-				readFileMeta(fileMeta, null);
-				// is there meta for other files left?
-				if (fileMeta.size() > 0) {
-					unresolvedFileMeta = fileMeta;
-				}
-			} catch (IOException e) {
-				logger.warn("error reading index.meta", e);
-			}
-		}
-		readParentMeta();
-		metaChecked = true;
-	}
-
-	/**
-	 * Read metadata from all known parent directories.
-	 *  
-	 */
-	public void readParentMeta() {
-		// check the parent directories for additional file meta
-		Directory dd = parent;
-		String path = dir.getName();
-		while (dd != null) {
-			if (((DocuDirectory) dd).hasUnresolvedFileMeta()) {
-				readFileMeta(((DocuDirectory) dd).unresolvedFileMeta, path);
-			}
-			// prepend parent dir path
-			path = dd.dir.getName() + "/" + path;
-			// become next parent
-			dd = dd.parent;
-		}
+	    meta.readMeta(this);
 	}
 
-	/**
-	 * Read metadata for the files in this directory.
-	 * 
-	 * Takes a Map with meta-information, adding the relative path before the
-	 * lookup.
-	 * 
-	 * @param fileMeta
-	 * @param relPath
-	 * @param fc
-	 *            fileClass
-	 */
-	protected void readFileMeta(Map<String,MetadataMap> fileMeta, String relPath) {
-		if (list == null) {
-			// there are no files
-			return;
-		}
-		String path = (relPath != null) ? (relPath + "/") : "";
-		// go through all file classes
-		for (FileClass fc: FileClass.values()) {
-			if (list.get(fc.ordinal()) == null) {
-				continue;
-			}
-			// iterate through the list of files in this directory
-			for (DocuDirent f: list.get(fc.ordinal())) {
-				// prepend path to the filename
-				String fn = path + f.getName();
-				// look up meta for this file and remove from dir
-				MetadataMap meta = fileMeta.remove(fn);
-				if (meta != null) {
-					// store meta in file
-					f.setFileMeta(meta);
-				}
-			}
-		}
-	}
 
-	protected void notifyChildMeta(MetadataMap childmeta) {
-		List<DocuDirectory> children = cache.getChildren(this.dirName, true);
-		if (children.size() > 0) {
-			/*for (DocuDirectory d: children) {
-				// TODO: finish this!
-				//((DocuDirectory) i.next()).readFileMeta()
-			}*/
-		}
-	}
+    /**
+     * check directory metadata.
+     *  
+     */
+    public void checkMeta() {
+        meta.checkMeta(this);
+    }
+
 
 	/**
 	 * Update access time.
@@ -512,43 +424,14 @@
 	}
 
 	/**
-	 * @return Hashtable
-	 */
-	public MetadataMap getDirMeta() {
-		return dirMeta;
-	}
-
-	/**
-	 * Checks metadata
-	 *  
-	 */
-	public void checkMeta() {
-		if (metaChecked) {
-			return;
-		} else {
-			readMeta();
-		}
-	}
-
-	/**
 	 * @return long
 	 */
 	public long getDirMTime() {
 		return dirMTime;
 	}
 
-	/**
-	 * Sets the dirMeta.
-	 * 
-	 * @param dirMeta
-	 *            The dirMeta to set
-	 */
-	public void setDirMeta(MetadataMap dirMeta) {
-		this.dirMeta = dirMeta;
-	}
-
-	public boolean hasUnresolvedFileMeta() {
-		return (this.unresolvedFileMeta != null);
-	}
+    public DirMeta getMeta() {
+        return meta;
+    }
 
 }
--- a/common/src/main/java/digilib/io/DocuDirent.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/DocuDirent.java	Wed Apr 03 21:07:57 2013 +0200
@@ -27,17 +27,11 @@
 
 import java.io.File;
 
-import digilib.meta.MetadataMap;
+import digilib.meta.FileMeta;
 
 public interface DocuDirent extends Comparable<Object> {
 
     /**
-     * Checks metadata and does something with it.
-     *  
-     */
-    public abstract void checkMeta();
-
-    /**
      * gets the (default) File
      * 
      * @return
@@ -45,12 +39,6 @@
     public abstract File getFile();
 
     /**
-     * Reads meta-data for this Fileset if there is any.
-     *  
-     */
-    public abstract void readMeta();
-
-    /**
      * The name of the file.
      * 
      * If this is a Fileset, the method returns the name of the default file
@@ -76,11 +64,22 @@
     public abstract void setParent(Directory parent);
 
     /**
+     * Reads meta-data for this Fileset if there is any.
+     *  
+     */
+    public abstract void readMeta();
+
+    /**
+     * Checks metadata and does something with it.
+     *  
+     */
+    public abstract void checkMeta();
+
+    /**
      * Returns the meta-data for this file(set).
-     * 
-     * @return HashMap
+     * @return
      */
-    public abstract MetadataMap getFileMeta();
+    public abstract FileMeta getMeta();
 
     /**
      * Sets the meta-data for this file(set) .
@@ -88,7 +87,7 @@
      * @param fileMeta
      *            The fileMeta to set
      */
-    public abstract void setFileMeta(MetadataMap fileMeta);
+    public abstract void setMeta(FileMeta fileMeta);
 
     /**
      * @return
--- a/common/src/main/java/digilib/io/DocuDirentImpl.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/DocuDirentImpl.java	Wed Apr 03 21:07:57 2013 +0200
@@ -26,14 +26,9 @@
  */
 
 import java.io.File;
-import java.util.Map;
-
-import org.apache.log4j.Logger;
 
 import digilib.io.FileOps.FileClass;
-import digilib.meta.IndexMetaAuthLoader;
-import digilib.meta.MetadataMap;
-import digilib.meta.IndexMetaLoader;
+import digilib.meta.FileMeta;
 
 /**
  * Abstract directory entry in a DocuDirectory.
@@ -45,47 +40,43 @@
 
 	/** the file class of this file */
 	protected static FileClass fileClass = FileClass.NONE;
-	/** HashMap with metadata */
-	protected MetadataMap fileMeta = null;
-	/** Is the Metadata valid */
-	protected boolean metaChecked = false;
+	/** metadata for this file */
+	protected FileMeta meta;
 	/** the parent directory */
 	protected Directory parent = null;
 
+    /* (non-Javadoc)
+     * @see digilib.io.DocuDirent#getInput()
+     */
+    public abstract File getFile();
+
 	/* (non-Javadoc)
      * @see digilib.io.DocuDirent#checkMeta()
      */
 	public abstract void checkMeta();
 
 	/* (non-Javadoc)
-     * @see digilib.io.DocuDirent#getInput()
+     * @see digilib.io.DocuDirent#getMeta()
      */
-	public abstract File getFile();
+    @Override
+    public FileMeta getMeta() {
+        // TODO Auto-generated method stub
+        return meta;
+    }
+
+    /* (non-Javadoc)
+     * @see digilib.io.DocuDirent#setMeta(digilib.meta.FileMeta)
+     */
+    @Override
+    public void setMeta(FileMeta fileMeta) {
+        this.meta = fileMeta;        
+    }
 
 	/* (non-Javadoc)
      * @see digilib.io.DocuDirent#readMeta()
      */
 	public void readMeta() {
-		if ((fileMeta != null) || (getFile() == null)) {
-			// there is already metadata or there is no file
-			return;
-		}
-		// metadata is in the file {filename}.meta
-		String fn = getFile().getAbsolutePath();
-		File mf = new File(fn + ".meta");
-		if (mf.canRead()) {
-			IndexMetaAuthLoader ml = new IndexMetaAuthLoader();
-			try {
-				// read meta file
-				Map<String, MetadataMap> meta = ml.loadUri(mf.toURI());
-				if (meta == null) {
-					return;
-				}
-				fileMeta = meta.get(getName());
-			} catch (Exception e) {
-				Logger.getLogger(this.getClass()).warn("error reading file .meta", e);
-			}
-		}
+	    meta.readMeta(this);
 	}
 
 	/* (non-Javadoc)
@@ -111,24 +102,10 @@
 	} 
 	
 	/* (non-Javadoc)
-     * @see digilib.io.DocuDirent#getFileMeta()
-     */
-	public MetadataMap getFileMeta() {
-		return fileMeta;
-	} 
-	
-	/* (non-Javadoc)
-     * @see digilib.io.DocuDirent#setFileMeta(digilib.io.MetadataMap)
-     */
-	public void setFileMeta(MetadataMap fileMeta) {
-		this.fileMeta = fileMeta;
-	} 
-	
-	/* (non-Javadoc)
      * @see digilib.io.DocuDirent#isMetaChecked()
      */
 	public boolean isMetaChecked() {
-		return metaChecked;
+		return meta.isChecked();
 	} 
 	
 	/**
--- a/common/src/main/java/digilib/io/ImageFileSet.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/io/ImageFileSet.java	Wed Apr 03 21:07:57 2013 +0200
@@ -26,14 +26,11 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Map;
-
-import org.apache.log4j.Logger;
 
 import digilib.io.FileOps.FileClass;
-import digilib.meta.IndexMetaAuthLoader;
+import digilib.meta.FileMeta;
+import digilib.meta.MetaFactory;
 import digilib.meta.MetadataMap;
-import digilib.meta.IndexMetaLoader;
 
 /**
  * @author casties
@@ -47,9 +44,9 @@
     protected File file = null;
     /** the file name */
     protected String name = null;
-	/** HashMap with metadata */
-	protected MetadataMap fileMeta = null;
-	/** Is the Metadata valid */
+    /** the FileMeta intance */
+    protected FileMeta meta = null;
+	/** is our metadata valid */
 	protected boolean metaChecked = false;
 	/** the parent directory */
 	protected Directory parentDir = null;
@@ -66,7 +63,8 @@
         // first dir is our parent
         parentDir = scaleDirs[0];
         this.file = file;
-        this.name = file.getName();
+        name = file.getName();
+        meta = MetaFactory.getFileMetaInstance();
         fill(scaleDirs, file);
     }
 
@@ -92,20 +90,6 @@
     }
 
     /* (non-Javadoc)
-     * @see digilib.io.DocuDirent#getFileMeta()
-     */
-    public MetadataMap getFileMeta() {
-    	return this.fileMeta;
-    }
-
-    /* (non-Javadoc)
-     * @see digilib.io.DocuDirent#setFileMeta(digilib.io.MetadataMap)
-     */
-    public void setFileMeta(MetadataMap fileMeta) {
-    	this.fileMeta = fileMeta;
-    }
-
-    /* (non-Javadoc)
      * @see digilib.io.DocuDirent#isMetaChecked()
      */
     public boolean isMetaChecked() {
@@ -213,32 +197,13 @@
         if (metaChecked) {
             return;
         }
-        if (fileMeta == null) {
-            // try to read metadata file
-            readMeta();
-            if (fileMeta == null) {
-                // try directory metadata
-                ((DocuDirectory) parentDir).checkMeta();
-                if (((DocuDirectory) parentDir).getDirMeta() != null) {
-                    fileMeta = ((DocuDirectory) parentDir).getDirMeta();
-                } else {
-                    // try parent directory metadata
-                    DocuDirectory gp = (DocuDirectory) parentDir.getParent();
-                    if (gp != null) {
-                        gp.checkMeta();
-                        if (gp.getDirMeta() != null) {
-                            fileMeta = gp.getDirMeta();
-                        }
-                    }
-                }
-            }
-        }
-        if (fileMeta == null) {
-            // no metadata available
-            metaChecked = true;
-            return;
-        }
+        // have the FileMeta class load and check
+        meta.checkMeta(this);
         metaChecked = true;
+        // take the metadata
+        MetadataMap fileMeta = meta.getFileMeta();
+        if (fileMeta == null) return;
+        // process the metadata
         float dpi = 0;
         float dpix = 0;
         float dpiy = 0;
@@ -302,32 +267,24 @@
      * @see digilib.io.DocuDirent#readMeta()
      */
 	public void readMeta() {
-		if ((fileMeta != null) || (file == null)) {
-			// there is already metadata or there is no file
-			return;
-		}
-		// metadata is in the file {filename}.meta
-		String fn = file.getAbsolutePath();
-		File mf = new File(fn + ".meta");
-		if (mf.canRead()) {
-			IndexMetaAuthLoader ml = new IndexMetaAuthLoader();
-			try {
-				// read meta file
-				Map<String, MetadataMap> meta = ml.loadUri(mf.toURI());
-				if (meta == null) {
-					return;
-				}
-				// file meta should be inside file tag
-				fileMeta = meta.get(name);
-				if (fileMeta == null) {
-				    // or there is only a meta tag
-				    fileMeta = meta.get("");
-				}
-			} catch (Exception e) {
-				Logger.getLogger(this.getClass()).warn("error reading file .meta", e);
-			}
-		}
+	    meta.readMeta(this);
 	}
 
+    /* (non-Javadoc)
+     * @see digilib.io.DocuDirent#getMeta()
+     */
+    @Override
+    public FileMeta getMeta() {
+        return this.meta;
+    }
+
+    /* (non-Javadoc)
+     * @see digilib.io.DocuDirent#setMeta(digilib.meta.FileMeta)
+     */
+    @Override
+    public void setMeta(FileMeta fileMeta) {
+        this.meta = fileMeta;
+    }
+
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/digilib/meta/DirMeta.java	Wed Apr 03 21:07:57 2013 +0200
@@ -0,0 +1,24 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import digilib.io.DocuDirectory;
+
+/**
+ * @author casties
+ *
+ */
+public interface DirMeta {
+
+    public boolean isChecked();
+
+    public void readMeta(DocuDirectory docuDirectory);
+
+    public void checkMeta(DocuDirectory docuDirectory);
+
+    public MetadataMap getDirMeta();
+
+    public void setDirMeta(MetadataMap dirMeta);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/digilib/meta/FileMeta.java	Wed Apr 03 21:07:57 2013 +0200
@@ -0,0 +1,29 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import digilib.io.DocuDirent;
+
+/**
+ * @author casties
+ *
+ */
+public interface FileMeta {
+
+    /**
+     * returns a MetadataMap with metadata for this File.
+     * 
+     * @param file
+     * @return
+     */
+    void readMeta(DocuDirent dirent);
+
+    MetadataMap getFileMeta();
+
+    void setFileMeta(MetadataMap fileMeta);
+
+    boolean isChecked();
+
+    void checkMeta(DocuDirent dirent);
+}
--- a/common/src/main/java/digilib/meta/IndexMetaAuthLoader.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/common/src/main/java/digilib/meta/IndexMetaAuthLoader.java	Wed Apr 03 21:07:57 2013 +0200
@@ -42,10 +42,11 @@
  */
 
 /**
- * Class loading index.meta files with metadata extracting some image file
+ * Class loading index.meta files extracting some image file
  * related information.
  * Extracts into the MetadataMap all tags in the meta/img tag as key-value
- * pairs.
+ * pairs and access conditions under the access key.
+ * 
  * Returns a map with filenames and MetadataMaps.
  * 
  * @see <a
@@ -60,21 +61,29 @@
 
     protected String metaTag = "meta";
     protected String fileTag = "file";
-    protected String fileNameTag = "name";
-    protected String filePathTag = "path";
+    protected String[] fileNameTag = {"file", "name"};
+    protected String[] filePathTag = {"file", "path"};
+    protected String[] fileMetaTag = {"file", "meta"};
     protected String imgTag = "img";
-    protected String accessTag = "access";
-    protected String accessConditionsTag = "access-conditions";
+    protected String[] accessTag = {"access-conditions", "access"};
+    protected String[] accessNameTag = {"access", "name"};
 
     private XMLStreamReader reader;
     private LinkedList<String> tags;
-    private String filename;
-    private String filepath;
+
+    protected Map<String, MetadataMap> files;
 
-    protected boolean matchesPath(LinkedList<String> tags, String[] path) {
+    /**
+     * Returns if the tag names in path match the current stack of tag names in tags.
+     * @param path
+     *   
+     * @return
+     */
+    protected boolean tagsMatchPath(String[] path) {
         try {
-            for (int i = path.length - 1; i >= 0; --i) {
-                if (!path[i].equals(tags.get(i))) {
+            int pl = path.length;
+            for (int i = pl - 1; i >= 0; --i) {
+                if (!path[i].equals(tags.get(pl-i-1))) {
                     return false;
                 }
             }
@@ -85,88 +94,139 @@
         return false;
     }
 
-    protected boolean readToMetaTag() throws XMLStreamException {
-        String thisTag = null;
-        String lastTag = null;
+    protected boolean readAll() throws XMLStreamException {
+        StringBuffer text = new StringBuffer();
+        MetadataMap fileMeta = new MetadataMap();
+        MetadataMap otherMeta = new MetadataMap();
+        String filename = null;
+        String filepath = null;
+        for (int event = reader.next(); event != XMLStreamConstants.END_DOCUMENT; event = reader.next()) {
+            if (event == XMLStreamConstants.START_ELEMENT) {
+                // get tag TODO: make namespace aware
+                String thisTag = reader.getLocalName();
+                // save on stack
+                tags.push(thisTag);
+                // new text content
+                text = new StringBuffer();
+                // look at tag
+                if (thisTag.equals(metaTag)) {
+                    /*
+                     * meta tag - read contents in new meta map
+                     */
+                    if (tagsMatchPath(fileMetaTag)) {
+                        fileMeta = readMetaTag(new MetadataMap());
+                    } else {
+                        otherMeta = readMetaTag(new MetadataMap());
+                    }
+                } else if (thisTag.equals(fileTag)) {
+                    /*
+                     * file tag - reset filenames
+                     */
+                    filename  = null;
+                    filepath  = null;
+                }
+            } else if (event == XMLStreamConstants.CHARACTERS) {
+                // append text nodes
+                text.append(reader.getText());
+            } else if (event == XMLStreamConstants.END_ELEMENT) {
+                // get tag TODO: make namespace aware
+                String thisTag = reader.getLocalName();
+                if (thisTag.equals(fileTag)) {
+                    /*
+                     * file tag - save file meta
+                     */
+                    if (!fileMeta.isEmpty()) {
+                        String fn = "";
+                        if (filename != null) {
+                            if (filepath != null) {
+                                fn = filepath + "/" + filename;
+                            } else {
+                                fn = filename;
+                            }
+                        }
+                        // save meta in file list
+                        files.put(fn, fileMeta);
+                    }                    
+                } else if (tagsMatchPath(fileNameTag)) {
+                    /*
+                     * file/name tag - record name
+                     */
+                    filename = text.toString();
+                } else if (tagsMatchPath(filePathTag)) {
+                    /*
+                     * file/path tag - record path
+                     */
+                    filepath = text.toString();
+                }
+                tags.pop();
+            }
+        }
+        if (!otherMeta.isEmpty()) {
+            // non-file meta put under key ""
+            files.put("", otherMeta);
+        }
+        return false;
+    }
+
+    /**
+     * Reads contents of current tag into map with the tag name as key and the content as value.
+     *   
+     * @param map
+     * @return
+     * @throws XMLStreamException
+     */
+    protected MetadataMap readTagToMap(MetadataMap map) throws XMLStreamException {
+        String outerTag = tags.peek();
         StringBuffer text = new StringBuffer();
         for (int event = reader.next(); event != XMLStreamConstants.END_DOCUMENT; event = reader.next()) {
             if (event == XMLStreamConstants.START_ELEMENT) {
-                // save last tag
-                lastTag = thisTag;
-                // init text content
-                text = new StringBuffer();
                 // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
+                String thisTag = reader.getLocalName();
                 // save on stack
                 tags.push(thisTag);
-                if (thisTag.equals(metaTag)) {
-                    return true;
-                } else if (thisTag.equals(fileTag)) {
-                    // reset filenames
-                    filename = null;
-                    filepath = null;
-                }
+                // new text content
+                text = new StringBuffer();
             } else if (event == XMLStreamConstants.CHARACTERS) {
                 text.append(reader.getText());
             } else if (event == XMLStreamConstants.END_ELEMENT) {
                 // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
-                tags.pop();
-                if (thisTag.equals(fileNameTag) && lastTag != null && lastTag.equals(fileTag)) {
-                    // name inside file tag -> record name
-                    filename = text.toString();
-                } else if (thisTag.equals(filePathTag) && lastTag != null && lastTag.equals(fileTag)) {
-                    // path inside file tag -> record path
-                    filepath = text.toString();
-                }
-            }
-        }
-        return false;
-    }
-
-    protected MetadataMap readTagToMap(MetadataMap map) throws XMLStreamException {
-        String thisTag = null;
-        String outerTag = tags.peek();
-        StringBuffer text = new StringBuffer();
-        for (int event = reader.next(); event != XMLStreamConstants.END_DOCUMENT; event = reader.next()) {
-            if (event == XMLStreamConstants.START_ELEMENT) {
-                // init text content
-                text = new StringBuffer();
-                // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
-                // save on stack
-                tags.push(thisTag);
-            } else if (event == XMLStreamConstants.CHARACTERS) {
-                text.append(reader.getText());
-            } else if (event == XMLStreamConstants.END_ELEMENT) {
-                // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
-                tags.pop();
+                String thisTag = reader.getLocalName();
                 if (thisTag.equals(outerTag)) {
                     // close outer tag
+                    tags.pop();
                     return map;
                 }
                 // put text in map under tag name
                 map.put(thisTag, text.toString());
+                tags.pop();
             }
         }
         return map;
     }
 
-    protected MetadataMap readAccessConditionsToMap(MetadataMap map) throws XMLStreamException {
-        String thisTag = null;
-        String outerTag = tags.peek();
+    /**
+     * Reads access tag into map.
+     * 
+     * @param map
+     * @return
+     * @throws XMLStreamException
+     */
+    protected MetadataMap readAccessToMap(MetadataMap map) throws XMLStreamException {
         StringBuffer text = new StringBuffer();
         String accType = null;
         String accName = null;
+        if (tagsMatchPath(accessTag)) {
+            // read attribute from current access tag
+            accType = reader.getAttributeValue(null, "type");
+        }
         for (int event = reader.next(); event != XMLStreamConstants.END_DOCUMENT; event = reader.next()) {
             if (event == XMLStreamConstants.START_ELEMENT) {
-                // init text content
-                text = new StringBuffer();
                 // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
+                String thisTag = reader.getLocalName();
                 // save on stack
                 tags.push(thisTag);
+                // new text content
+                text = new StringBuffer();
                 // save type attribute of access tag
                 if (thisTag.equals(accessTag)) {
                     accType = reader.getAttributeValue(null, "type");
@@ -175,16 +235,16 @@
                 text.append(reader.getText());
             } else if (event == XMLStreamConstants.END_ELEMENT) {
                 // get tag TODO: make namespace aware
-                thisTag = reader.getLocalName();
-                tags.pop();
-                if (thisTag.equals(outerTag)) {
-                    // close outer tag
-                    return map;
-                } else if (thisTag.equals("name") && tags.size() > 0 && tags.peek().equals(accessTag)) {
-                    // access/name
+                String thisTag = reader.getLocalName();
+                if (tagsMatchPath(accessNameTag)) {
+                    /*
+                     * access/name tag
+                     */
                     accName = text.toString();
-                } else if (thisTag.equals(accessTag)) {
-                    // end of access tag
+                } else if (tagsMatchPath(accessTag)) {
+                    /*
+                     * access tag - we're done
+                     */
                     if (accType == null) {
                         // no access type
                         return map;
@@ -197,10 +257,14 @@
                     } else {
                         // type != free but no name
                         logger.error("access type="+accType+" but no name!");
+                        tags.pop();
                         return map;
                     }
                     map.put("access", access);
+                    tags.pop();
+                    return map;
                 }
+                tags.pop();
             }
         }
         return map;
@@ -215,19 +279,25 @@
                 // save on stack
                 tags.push(thisTag);
                 if (thisTag.equals(imgTag)) {
+                    /*
+                     * img tag
+                     */
                     map = readTagToMap(map);
-                }
-                if (thisTag.equals(accessConditionsTag)) {
-                    map = readAccessConditionsToMap(map);
+                } else if (tagsMatchPath(accessTag)) {
+                    /*
+                     * access tag
+                     */
+                    map = readAccessToMap(map);
                 }
             } else if (event == XMLStreamConstants.END_ELEMENT) {
                 // get tag TODO: make namespace aware
                 thisTag = reader.getLocalName();
-                tags.pop();
                 if (thisTag.equals(metaTag)) {
                     // close meta tag
+                    tags.pop();
                     return map;
                 }
+                tags.pop();
             }
         }
         return map;
@@ -240,26 +310,14 @@
      * @throws IOException
      */
     public Map<String, MetadataMap> loadUri(URI uri) throws IOException {
-        Map<String, MetadataMap> files = new HashMap<String, MetadataMap>();
+        files = new HashMap<String, MetadataMap>();
         try {
             InputStream in = uri.toURL().openStream();
             XMLInputFactory factory = XMLInputFactory.newInstance();
             reader = factory.createXMLStreamReader(in);
             // start reading
             tags = new LinkedList<String>();
-            while (readToMetaTag()) {
-                String fn = "";
-                if (filename != null) {
-                    if (filepath != null) {
-                        fn = filepath + "/" + filename;
-                    } else {
-                        fn = filename;
-                    }
-                }
-                MetadataMap meta = new MetadataMap();
-                meta = readMetaTag(meta);
-                // save meta in file list
-                files.put(fn, meta);
+            while (readAll()) {
             }
         } catch (MalformedURLException e) {
             logger.error("Malformed URL!");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/digilib/meta/IndexMetaDirMeta.java	Wed Apr 03 21:07:57 2013 +0200
@@ -0,0 +1,167 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import digilib.io.Directory;
+import digilib.io.DocuDirectory;
+import digilib.io.DocuDirent;
+import digilib.io.FileOps.FileClass;
+
+/**
+ * @author casties
+ *
+ */
+public class IndexMetaDirMeta implements DirMeta {
+    /** Log4J logger */
+    protected static Logger logger = Logger.getLogger(IndexMetaDirMeta.class);
+
+    private MetadataMap dirMeta = null;
+
+    /** state of metadata is valid */
+    private boolean metaChecked = false;
+
+    /** unresolved file metadata */
+    private Map<String, MetadataMap> unresolvedFileMeta = null;
+
+
+    @Override
+    public void readMeta(DocuDirectory dir) {
+        // read directory metadata
+        File mf = new File(dir.getDir(), "index.meta");
+        if (mf.canRead()) {
+            IndexMetaAuthLoader ml = new IndexMetaAuthLoader();
+            try {
+                // read directory meta file
+                Map<String, MetadataMap> meta = ml.loadUri(mf.toURI());
+                if (meta == null) {
+                    return;
+                }
+                // meta for the directory itself is in the "" bin
+                dirMeta = meta.remove("");
+                // read meta for files in this directory
+                readFileMeta(dir, meta, null);
+                // is there meta for other files left?
+                if (meta.size() > 0) {
+                    unresolvedFileMeta = meta;
+                }
+            } catch (IOException e) {
+                logger.warn("error reading index.meta", e);
+            }
+        }
+        readParentMeta(dir);
+        metaChecked = true;
+    }
+
+    /**
+     * Read metadata from all known parent directories.
+     * @param dir 
+     *  
+     */
+    public void readParentMeta(DocuDirectory dir) {
+        // check the parent directories for additional file meta
+        Directory dd = dir.getParent();
+        String path = dir.getDir().getName();
+        while (dd != null) {
+            IndexMetaDirMeta dm = (IndexMetaDirMeta) ((DocuDirectory) dd).getMeta();
+            if (dm.hasUnresolvedFileMeta()) {
+                readFileMeta(dir, dm.getUnresolvedFileMeta(), path);
+            }
+            // prepend parent dir path
+            path = dd.getDir().getName() + "/" + path;
+            // become next parent
+            dd = dd.getParent();
+        }
+    }
+
+    /**
+     * Read metadata for the files in this directory.
+     * 
+     * Takes a Map with meta-information, adding the relative path before the
+     * lookup.
+     * @param dir 
+     * 
+     * @param fileMeta
+     * @param relPath
+     * @param fc
+     *            fileClass
+     */
+    protected void readFileMeta(DocuDirectory dir, Map<String,MetadataMap> fileMeta, String relPath) {
+        if (dir.size() == 0) {
+            // there are no files
+            return;
+        }
+        String path = (relPath != null) ? (relPath + "/") : "";
+        // go through all file classes
+        for (FileClass fc: FileClass.values()) {
+            int ds = dir.size(fc);
+            if (ds == 0) {
+                continue;
+            }
+            // iterate through the list of files in this directory
+            for (int i = 0; i < ds; ++i) {
+                DocuDirent f = dir.get(i, fc);
+                // prepend path to the filename
+                String fn = path + f.getName();
+                // look up meta for this file and remove from dir
+                MetadataMap meta = fileMeta.remove(fn);
+                if (meta != null) {
+                    // store meta in file
+                    f.getMeta().setFileMeta(meta);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks metadata
+     *  
+     */
+    public void checkMeta(DocuDirectory dir) {
+        if (metaChecked) {
+            return;
+        } else {
+            readMeta(dir);
+        }
+    }
+
+    /**
+     * @return Hashtable
+     */
+    public MetadataMap getDirMeta() {
+        return dirMeta;
+    }
+
+    /**
+     * Sets the dirMeta.
+     * 
+     * @param dirMeta
+     *            The dirMeta to set
+     */
+    public void setDirMeta(MetadataMap dirMeta) {
+        this.dirMeta = dirMeta;
+    }
+
+    protected boolean hasUnresolvedFileMeta() {
+        return (this.unresolvedFileMeta != null);
+    }
+
+    protected Map<String, MetadataMap> getUnresolvedFileMeta() {
+        return this.unresolvedFileMeta;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return this.metaChecked;
+    }
+
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/digilib/meta/IndexMetaFileMeta.java	Wed Apr 03 21:07:57 2013 +0200
@@ -0,0 +1,107 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import digilib.io.DocuDirectory;
+import digilib.io.DocuDirent;
+
+/**
+ * @author casties
+ * 
+ */
+public class IndexMetaFileMeta implements FileMeta {
+    /** Log4J logger */
+    protected static Logger logger = Logger.getLogger(IndexMetaFileMeta.class);
+
+    protected MetadataMap fileMeta;
+
+    /** state of metadata is valid */
+    private boolean metaChecked = false;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.meta.DocuMeta#readMeta(digilib.io.DocuDirent)
+     */
+    @Override
+    public void readMeta(DocuDirent dirent) {
+        // read file metadata
+        File f = dirent.getFile();
+        if (fileMeta != null || f == null) {
+            // meta exists or file doesn't
+            return;
+        }
+        String fn = f.getAbsolutePath();
+        File mf = new File(fn + ".meta");
+        if (mf.canRead()) {
+            IndexMetaAuthLoader ml = new IndexMetaAuthLoader();
+            try {
+                // read meta file
+                Map<String, MetadataMap> meta = ml.loadUri(mf.toURI());
+                if (meta != null) {
+                    // meta for file either directly in meta-tag
+                    fileMeta = meta.get("");
+                    if (fileMeta == null) {
+                        // or under file's name
+                        fileMeta = meta.get(dirent.getName());
+                    }
+                }
+            } catch (Exception e) {
+                logger.warn("error reading file .meta", e);
+            }
+        }
+    }
+
+    @Override
+    public void checkMeta(DocuDirent file) {
+        if (metaChecked) return;
+        if (fileMeta == null) {
+            // try to read metadata file
+            readMeta(file);
+            if (fileMeta == null) {
+                // try directory metadata
+                DocuDirectory dd = (DocuDirectory) file.getParent();
+                // running checkmeta also distributes meta to files
+                dd.checkMeta();
+                if (fileMeta == null) {
+                    if (dd.getMeta().getDirMeta() != null) {
+                        fileMeta = dd.getMeta().getDirMeta();
+                    } else {
+                        // try parent directory metadata (just one level up)
+                        DocuDirectory pdd = (DocuDirectory) dd.getParent();
+                        if (pdd != null) {
+                            pdd.checkMeta();
+                            if (pdd.getMeta().getDirMeta() != null) {
+                                fileMeta = pdd.getMeta().getDirMeta();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        metaChecked = true;
+    }
+
+    @Override
+    public MetadataMap getFileMeta() {
+        return fileMeta;
+    }
+
+    @Override
+    public void setFileMeta(MetadataMap fileMeta) {
+        this.fileMeta = fileMeta;
+    }
+
+    @Override
+    public boolean isChecked() {
+        // TODO Auto-generated method stub
+        return this.metaChecked;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/digilib/meta/MetaFactory.java	Wed Apr 03 21:07:57 2013 +0200
@@ -0,0 +1,58 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * @author casties
+ * 
+ */
+public class MetaFactory {
+    /** Log4J logger */
+    protected static Logger logger = Logger.getLogger(MetaFactory.class);
+
+    /** DirMeta implementation class */
+    protected static Class<DirMeta> dirMetaClass;
+
+    /** FileMeta implementation class */
+    protected static Class<FileMeta> fileMetaClass;
+
+    public static FileMeta getFileMetaInstance() {
+        FileMeta mo = null;
+        try {
+            mo = fileMetaClass.newInstance();
+        } catch (Exception e) {
+            logger.error("Unable to create FileMeta instance!", e);
+        }
+        return mo;
+    }
+    
+    public static DirMeta getDirMetaInstance() {
+        DirMeta mo = null;
+        try {
+            mo = dirMetaClass.newInstance();
+        } catch (Exception e) {
+            logger.error("Unable to create DirMeta instance!", e);
+        }
+        return mo;
+    }
+
+    /**
+     * @param dirMetaClass the dirMetaClass to set
+     */
+    public static void setDirMetaClass(Class<DirMeta> dirMetaClass) {
+        MetaFactory.dirMetaClass = dirMetaClass;
+    }
+
+    /**
+     * @param fileMetaClass the fileMetaClass to set
+     */
+    public static void setFileMetaClass(Class<FileMeta> fileMetaClass) {
+        MetaFactory.fileMetaClass = fileMetaClass;
+    }
+    
+
+}
--- a/servlet/src/main/java/digilib/conf/DigilibServletConfiguration.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/servlet/src/main/java/digilib/conf/DigilibServletConfiguration.java	Wed Apr 03 21:07:57 2013 +0200
@@ -35,6 +35,9 @@
 
 import digilib.image.DocuImageImpl;
 import digilib.io.FileOps;
+import digilib.meta.DirMeta;
+import digilib.meta.FileMeta;
+import digilib.meta.MetaFactory;
 import digilib.servlet.ServletOps;
 import digilib.util.Parameter;
 import digilib.util.XMLListLoader;
@@ -132,6 +135,16 @@
         newParameter("max-waiting-threads", new Integer(20), null, 'f');
         // timeout for worker threads (ms)
         newParameter("worker-timeout", new Integer(60000), null, 'f');
+        // allow image toolkit to use disk cache
+        newParameter("img-diskcache-allowed", Boolean.TRUE, null, 'f');
+        // default type of error message (image, text, code)
+        newParameter("default-errmsg-type", "image", null, 'f');
+        // FileMeta implementation
+        newParameter("filemeta-class", "digilib.meta.IndexMetaFileMeta", null, 'f');
+        // DirMeta implementation
+        newParameter("dirmeta-class", "digilib.meta.IndexMetaDirMeta", null, 'f');
+        
+        // TODO: move pdf-stuff to its own config
         // number of pdf-generation threads
         newParameter("pdf-worker-threads", new Integer(1), null, 'f');
         // max number of waiting pdf-generation threads
@@ -144,10 +157,6 @@
         newParameter("pdf-temp-dir", "pdf_temp", null, 'f');
         // PDF generation cache directory
         newParameter("pdf-cache-dir", "pdf_cache", null, 'f');
-        // allow image toolkit to use disk cache
-        newParameter("img-diskcache-allowed", Boolean.TRUE, null, 'f');
-        // default type of error message (image, text, code)
-        newParameter("default-errmsg-type", "image", null, 'f');
     }
 
     /**
@@ -238,7 +247,15 @@
         }
         // initialise static DocuImage class instance
         DigilibServletConfiguration.docuImageClass = (Class<DocuImageImpl>) Class.forName(getAsString("docuimage-class"));
+        setValue("servlet.docuimage.class", DigilibServletConfiguration.docuImageClass);
         setValue("servlet.docuimage.version", getDocuImageInstance().getVersion());
+        // initialise MetaFactory
+        Class<FileMeta> fileMetaClass = (Class<FileMeta>) Class.forName(getAsString("filemeta-class"));
+        newParameter("servlet.filemeta.class", fileMetaClass, null, 's');
+        MetaFactory.setFileMetaClass(fileMetaClass);
+        Class<DirMeta> dirMetaClass = (Class<DirMeta>) Class.forName(getAsString("dirmeta-class"));
+        newParameter("servlet.dirmeta.class", dirMetaClass, null, 's');
+        MetaFactory.setDirMetaClass(dirMetaClass);
     }
 
 }
--- a/servlet3/src/main/java/digilib/servlet/Scaler.java	Wed Apr 03 10:36:07 2013 +0200
+++ b/servlet3/src/main/java/digilib/servlet/Scaler.java	Wed Apr 03 21:07:57 2013 +0200
@@ -59,7 +59,7 @@
     private static final long serialVersionUID = 5289386646192471549L;
 
     /** digilib servlet version (for all components) */
-    public static final String version = "2.1.5 async";
+    public static final String version = "2.1.6 async";
 
     /** servlet error codes */
     public static enum Error {
--- a/webapp/src/main/webapp/sample-images/p0005.jpg.meta	Wed Apr 03 10:36:07 2013 +0200
+++ b/webapp/src/main/webapp/sample-images/p0005.jpg.meta	Wed Apr 03 21:07:57 2013 +0200
@@ -4,12 +4,14 @@
     <img>
       <original-dpi>370</original-dpi>
     </img>
-    <copyright resource="digital-image">
-      <owner>
-        <name>Max Planck Institute for the History of Science, Library</name>
-        <url>http://www.mpiwg-berlin.mpg.de/</url>
-      </owner>
-      <license type="cc-by-sa"/>
-    </copyright>
+    <access-conditions>
+      <copyright resource="digital-image">
+	<owner>
+          <name>Max Planck Institute for the History of Science, Library</name>
+          <url>http://www.mpiwg-berlin.mpg.de/</url>
+	</owner>
+	<license type="cc-by-sa"/>
+      </copyright>
+    </access-conditions>
   </meta>
 </resource>