- add moved class to core-metadata

git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1034324 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/CacheableMetaData.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/CacheableMetaData.java
new file mode 100644
index 0000000..0222a05
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/CacheableMetaData.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Cached, read-only meta data.
+ */
+public class CacheableMetaData implements MetaData {
+
+    /**
+     * Maps keys to value arrays.
+     */
+    private Map key2values = new HashMap();
+    private long lastModified;
+    private ElementSet elementSet;
+
+    /**
+     * @param meta The meta data to build this object from.
+     */
+    public CacheableMetaData(MetaData meta) {
+        this.elementSet = meta.getElementSet();
+        String[] keys = meta.getAvailableKeys();
+        try {
+            this.lastModified = meta.getLastModified();
+            for (int i = 0; i < keys.length; i++) {
+                String[] values = meta.getValues(keys[i]);
+                this.key2values.put(keys[i], values);
+            }
+        } catch (MetaDataException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void addValue(String key, String value) throws MetaDataException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void forcedReplaceBy(MetaData other) throws MetaDataException {
+        throw new UnsupportedOperationException();
+    }
+
+    public String[] getAvailableKeys() {
+        Set keys = key2values.keySet();
+        return (String[]) keys.toArray(new String[keys.size()]);
+    }
+
+    public ElementSet getElementSet() {
+        return this.elementSet;
+    }
+
+    public String getFirstValue(String key) throws MetaDataException {
+        String value = null;
+        String[] values = getValues(key);
+        if (values.length > 0) {
+            value = values[0];
+        }
+        return value;
+    }
+
+    public long getLastModified() throws MetaDataException {
+        return this.lastModified;
+    }
+    
+    private String[] possibleKeys;
+
+    public String[] getPossibleKeys() {
+        if (this.possibleKeys == null) {
+            Element[] elements = getElementSet().getElements();
+            this.possibleKeys = new String[elements.length];
+            for (int i = 0; i < possibleKeys.length; i++) {
+                possibleKeys[i] = elements[i].getName();
+            }
+        }
+        return this.possibleKeys;
+    }
+
+    public String[] getValues(String key) throws MetaDataException {
+        if (this.key2values.containsKey(key)) {
+            return (String[]) this.key2values.get(key);
+        }
+        else {
+            return new String[0];
+        }
+    }
+
+    public boolean isValidAttribute(String key) {
+        return Arrays.asList(getPossibleKeys()).contains(key);
+    }
+
+    public void removeAllValues(String key) throws MetaDataException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void replaceBy(MetaData other) throws MetaDataException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setValue(String key, String value) throws MetaDataException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/Element.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/Element.java
new file mode 100644
index 0000000..2eb5c03
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/Element.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * A meta data element.
+ */
+public interface Element {
+
+    /**
+     * @return the name of the element.
+     */
+    String getName();
+
+    /**
+     * @return if the element can have multiple values.
+     */
+    boolean isMultiple();
+
+    /**
+     * @return the description of the element.
+     */
+    String getDescription();
+
+    /**
+     * @return if the element value can be edited.
+     */
+    boolean isEditable();
+
+    /**
+     * Copy all values if the meta data are copied.
+     */
+    int ONCOPY_COPY = 0;
+    
+    /**
+     * Don't copy the values of this element if the meta data are copied.
+     */
+    int ONCOPY_IGNORE = 1;
+
+    /**
+     * Delete all values of this element if the meta data are copied.
+     */
+    int ONCOPY_DELETE = 2;
+
+    /**
+     * @return The action to be taken when meta data are copied from one owner to another.
+     */
+    int getActionOnCopy();
+    
+    /**
+     * @return If this element shall be included in search queries.
+     */
+    boolean isSearchable();
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSet.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSet.java
new file mode 100644
index 0000000..bbc1a61
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSet.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * Definition of a set of meta data elements.
+ */
+public interface ElementSet {
+    
+    /**
+     * @return The supported elements.
+     */
+    Element[] getElements();
+    
+    /**
+     * @param name The name.
+     * @return The element.
+     * @throws MetaDataException if the element with this name does not exist.
+     */
+    Element getElement(String name) throws MetaDataException;
+    
+    /**
+     * @return The namespace URI of this element set.
+     */
+    String getNamespaceUri();
+    
+    /**
+     * Checks if an element with a certain name is contained.
+     * @param name The name.
+     * @return A boolean value.
+     */
+    boolean containsElement(String name);
+    
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSetWrapper.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSetWrapper.java
new file mode 100644
index 0000000..3a57a6d
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementSetWrapper.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lenya.cms.repository.metadata.ElementSet;
+import org.apache.lenya.cms.repository.metadata.MetaDataException;
+
+public class ElementSetWrapper implements org.apache.lenya.cms.metadata.ElementSet {
+
+    private org.apache.lenya.cms.repository.metadata.ElementSet delegate;
+    private Map<String, ElementWrapper> elements = new HashMap<String, ElementWrapper>();
+
+    public ElementSetWrapper(ElementSet delegate) {
+        super();
+        this.delegate = delegate;
+    }
+
+    public boolean containsElement(String name) {
+        return this.delegate.containsElement(name);
+    }
+
+    public Element getElement(String name) throws org.apache.lenya.cms.metadata.MetaDataException {
+        ElementWrapper wrapper = this.elements.get(name);
+        if (wrapper == null) {
+            try {
+                wrapper = new ElementWrapper(this.delegate.getElement(name));
+            } catch (MetaDataException e) {
+                throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+            }
+            this.elements.put(name, wrapper);
+        }
+        return wrapper;
+    }
+
+    public Element[] getElements() {
+        org.apache.lenya.cms.repository.metadata.Element[] elements = this.delegate.getElements();
+        Element[] wrappers = new Element[elements.length];
+        for (int i = 0; i < elements.length; i++) {
+            try {
+                wrappers[i] = getElement(elements[i].getName());
+            } catch (org.apache.lenya.cms.metadata.MetaDataException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return wrappers;
+    }
+
+    public String getNamespaceUri() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementWrapper.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementWrapper.java
new file mode 100644
index 0000000..5f43b69
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/ElementWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+import org.apache.lenya.cms.repository.metadata.Element;
+
+public class ElementWrapper implements org.apache.lenya.cms.metadata.Element {
+    
+    private org.apache.lenya.cms.repository.metadata.Element delegate;
+
+    public ElementWrapper(Element delegate) {
+        this.delegate = delegate;
+    }
+
+    public int getActionOnCopy() {
+        return this.delegate.getActionOnCopy();
+    }
+
+    public String getDescription() {
+        return this.delegate.getDescription();
+    }
+
+    public String getName() {
+        return this.delegate.getName();
+    }
+
+    public boolean isEditable() {
+        return this.delegate.isEditable();
+    }
+
+    public boolean isMultiple() {
+        return this.delegate.isMultiple();
+    }
+
+    public boolean isSearchable() {
+        return this.delegate.isSearchable();
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaData.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaData.java
new file mode 100644
index 0000000..2207aa4
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaData.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * Generic meta data interface.
+ * 
+ * @version $Id$
+ */
+public interface MetaData {
+    
+    /**
+     * Returns the values for a certain key.
+     * @param key The key.
+     * @return An array of strings.
+     * @throws MetaDataException when something went wrong.
+     */
+    String[] getValues(String key) throws MetaDataException;
+
+    /**
+     * Returns the first value for a certain key.
+     * @param key The key.
+     * @return A string or <code>null</code> if no value is set for this key.
+     * @throws MetaDataException if an error occurs.
+     */
+    String getFirstValue(String key) throws MetaDataException;
+    
+    /**
+     * Get all available keys.
+     * @return The keys available in this MetaData object.
+     */
+    String[] getAvailableKeys();
+    
+    /**
+     * Sets the value for a certain key. All existing values will be removed.
+     * @param key The key.
+     * @param value The value to set.
+     * @throws MetaDataException when something went wrong.
+     */
+    void setValue(String key, String value) throws MetaDataException;
+    
+    /**
+     * Addds a value for a certain key. The existing values will not be removed.
+     * @param key The key.
+     * @param value The value to add.
+     * @throws MetaDataException if there's already a value set and the element doesn't support multiple values.
+     */
+    void addValue(String key, String value) throws MetaDataException;
+
+    /**
+     * Replace the contents of the current meta data by the contents of other.
+     * @param other The other meta data manager.
+     * @throws MetaDataException if an error occurs.
+     */
+    void replaceBy(MetaData other) throws MetaDataException;
+    
+    /**
+     * Replace the contents of the current meta data by the contents of other.
+     * All meta data is replaced, disregarding the rules given by element.getActionOnCopy().
+     * @param other The other meta data manager.
+     * @throws MetaDataException if an error occurs.
+     */
+    void forcedReplaceBy(MetaData other) throws MetaDataException;
+    
+    /**
+     * @return All keys that can be used.
+     */
+    String[] getPossibleKeys();
+    
+    /**
+     * Checks if a key represents a valid metadata attribute.
+     * @param key The key.
+     * @return A boolean value.
+     */
+    boolean isValidAttribute(String key);
+    
+    /**
+     * Get last modification date.
+     * @return last modification date
+     * @throws MetaDataException if an error occurs.
+     */
+     long getLastModified() throws MetaDataException;
+     
+     /**
+     * @return The element set this meta data object belongs to.
+     */
+    ElementSet getElementSet();
+    
+    /**
+     * Removes all values for a certain key.
+     * @param key The key.
+     * @throws MetaDataException if the key is not supported.
+     */
+    void removeAllValues(String key) throws MetaDataException;
+     
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataCache.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataCache.java
new file mode 100644
index 0000000..6aa3037
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataCache.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.excalibur.store.impl.MRUMemoryStore;
+import org.apache.lenya.cms.metadata.CacheableMetaData;
+import org.apache.lenya.cms.metadata.MetaData;
+import org.apache.lenya.cms.metadata.MetaDataException;
+
+/**
+ * Cache for meta data.
+ */
+public class MetaDataCache {
+
+    public static final String ROLE = MetaDataCache.class.getName();
+    protected static final String STORE_ROLE = MetaDataCache.ROLE + "Store";
+    private MRUMemoryStore store;
+
+    /**
+     * Get a meta data object from the cache.
+     * @param document The document.
+     * @param namespaceUri The namespace URI.
+     * @return A meta data object.
+     * @throws MetaDataException if an error occurs.
+     */
+    public synchronized MetaData getMetaData(String cacheKey, MetaData meta, String namespaceUri)
+            throws MetaDataException {
+        MRUMemoryStore store = getStore();
+        String key = getCacheKey(cacheKey, namespaceUri);
+
+        MetaData cachedMeta = null;
+        if (store.containsKey(key)) {
+            cachedMeta = (MetaData) store.get(key);
+            if (meta.getLastModified() > cachedMeta.getLastModified()) {
+                cachedMeta = null;
+            }
+        }
+        if (cachedMeta == null) {
+            cachedMeta = new CacheableMetaData(meta);
+            store.hold(key, cachedMeta);
+        }
+        return cachedMeta;
+    }
+
+    protected String getCacheKey(String cacheKey, String namespaceUri) {
+        return cacheKey + ":" + namespaceUri;
+    }
+
+    protected MRUMemoryStore getStore() {
+        if (this.store == null) {
+            synchronized (this) {
+                this.store = (MRUMemoryStore) WebAppContextUtils.getCurrentWebApplicationContext()
+                        .getBean(STORE_ROLE);
+            }
+        }
+        return this.store;
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataException.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataException.java
new file mode 100644
index 0000000..fdec9dd
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataException.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * Meta data exception.
+ */
+public class MetaDataException extends Exception {
+
+    /**
+     * 
+     */
+    public MetaDataException() {
+        super();
+    }
+
+    /**
+     * @param arg0
+     * @param arg1
+     */
+    public MetaDataException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    /**
+     * @param arg0
+     */
+    public MetaDataException(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * @param arg0
+     */
+    public MetaDataException(Throwable arg0) {
+        super(arg0);
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataOwner.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataOwner.java
new file mode 100644
index 0000000..16ba4c5
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataOwner.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * Owner of meta-data.
+ *
+ * @version $Id$
+ */
+public interface MetaDataOwner {
+
+    /**
+     * Returns a meta data object.
+     * @param namespaceUri The namespace URI.
+     * @return A meta data object.
+     * @throws MetaDataException if an error occurs.
+     */
+    MetaData getMetaData(String namespaceUri) throws MetaDataException;
+    
+    /**
+     * Returns the URIs of the meta data currently supported by the owner.
+     * @return An array of strings.
+     * @throws MetaDataException if an error occurs.
+     */
+    String[] getMetaDataNamespaceUris() throws MetaDataException;
+    
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistry.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistry.java
new file mode 100644
index 0000000..db06f2a
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistry.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+/**
+ * Meta data registry.
+ */
+public interface MetaDataRegistry {
+    
+    /**
+     * The service role.
+     */
+    String ROLE = MetaDataRegistry.class.getName();
+
+    /**
+     * @param namespaceUri The namespace URI of the element set.
+     * @return the element set.
+     * @throws MetaDataException if an error occurs. 
+     */
+    ElementSet getElementSet(String namespaceUri) throws MetaDataException;
+    
+    /**
+     * Checks if an element set is registered.
+     * @param namespaceUri The namespace URI.
+     * @return A boolean value.
+     * @throws MetaDataException if an error occurs.
+     */
+    boolean isRegistered(String namespaceUri) throws MetaDataException;
+    
+    /**
+     * @return The registered namespace URIs.
+     * @throws MetaDataException if an error occurs.
+     */
+    String[] getNamespaceUris() throws MetaDataException;
+    
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistryWrapper.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistryWrapper.java
new file mode 100644
index 0000000..17d1ce3
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataRegistryWrapper.java
@@ -0,0 +1,54 @@
+package org.apache.lenya.cms.metadata;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lenya.cms.repository.metadata.MetaDataException;
+
+public class MetaDataRegistryWrapper implements MetaDataRegistry {
+
+    private org.apache.lenya.cms.repository.metadata.MetaDataRegistry metaDataRegistry;
+
+    private Map<String, ElementSetWrapper> elementSets = new HashMap<String, ElementSetWrapper>();
+
+    public ElementSet getElementSet(String namespaceUri)
+            throws org.apache.lenya.cms.metadata.MetaDataException {
+        ElementSetWrapper wrapper = this.elementSets.get(namespaceUri);
+        if (wrapper == null) {
+            try {
+                wrapper = new ElementSetWrapper(this.metaDataRegistry.getElementSet(namespaceUri));
+            } catch (MetaDataException e) {
+                throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+            }
+            this.elementSets.put(namespaceUri, wrapper);
+        }
+        return wrapper;
+    }
+
+    public String[] getNamespaceUris() throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            return this.metaDataRegistry.getNamespaceUris();
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public boolean isRegistered(String namespaceUri)
+            throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            return this.metaDataRegistry.isRegistered(namespaceUri);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public void setRepositoryMetaDataRegistry(
+            org.apache.lenya.cms.repository.metadata.MetaDataRegistry metaDataRegistry) {
+        this.metaDataRegistry = metaDataRegistry;
+    }
+
+    public org.apache.lenya.cms.repository.metadata.MetaDataRegistry getRepositoryMetaDataRegistry() {
+        return this.metaDataRegistry;
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataWrapper.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataWrapper.java
new file mode 100644
index 0000000..9ed3a50
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/MetaDataWrapper.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.metadata;
+
+import org.apache.lenya.cms.repository.metadata.MetaDataException;
+
+public class MetaDataWrapper implements MetaData {
+
+    private org.apache.lenya.cms.repository.metadata.MetaData delegate;
+
+    public MetaDataWrapper(org.apache.lenya.cms.repository.metadata.MetaData delegate) {
+        this.delegate = delegate;
+    }
+
+    protected org.apache.lenya.cms.repository.metadata.MetaData getDelegate() {
+        return this.delegate;
+    }
+
+    public void addValue(String key, String value)
+            throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            this.delegate.addValue(key, value);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public void forcedReplaceBy(MetaData other)
+            throws org.apache.lenya.cms.metadata.MetaDataException {
+        MetaDataWrapper wrapper = (MetaDataWrapper) other;
+        try {
+            this.delegate.forcedReplaceBy(wrapper.getDelegate());
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public String[] getAvailableKeys() {
+        return this.delegate.getAvailableKeys();
+    }
+
+    private ElementSet elements;
+
+    public ElementSet getElementSet() {
+        if (this.elements == null) {
+            this.elements = new ElementSetWrapper(this.delegate.getElementSet());
+        }
+        return this.elements;
+    }
+
+    public String getFirstValue(String key) throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            return this.delegate.getFirstValue(key);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public long getLastModified() throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            return this.delegate.getLastModified();
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public String[] getPossibleKeys() {
+        return this.delegate.getPossibleKeys();
+    }
+
+    public String[] getValues(String key) throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            return this.delegate.getValues(key);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+    }
+
+    public boolean isValidAttribute(String key) {
+        return this.delegate.isValidAttribute(key);
+    }
+
+    public void removeAllValues(String key) throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            this.delegate.removeAllValues(key);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+
+    }
+
+    public void replaceBy(MetaData other) throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            MetaDataWrapper wrapper = (MetaDataWrapper) other;
+            this.delegate.replaceBy(wrapper.getDelegate());
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+
+    }
+
+    public void setValue(String key, String value)
+            throws org.apache.lenya.cms.metadata.MetaDataException {
+        try {
+            this.delegate.setValue(key, value);
+        } catch (MetaDataException e) {
+            throw new org.apache.lenya.cms.metadata.MetaDataException(e);
+        }
+
+    }
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCore.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCore.java
new file mode 100644
index 0000000..772c9eb
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCore.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.lenya.cms.metadata.dublincore;
+
+import org.apache.lenya.cms.metadata.MetaData;
+
+/**
+ * <p>
+ * Dublin core metadata interface.
+ * </p>
+ * <p>
+ * The descriptions are citing the <a href="http://www.dublincore.org">Dublin Core website </a>.
+ * </p>
+ * 
+ * @version $Id$
+ */
+public interface DublinCore extends MetaData {
+
+    /**
+     * The dublin core elements namespace.
+     */
+    String DC_NAMESPACE = "http://purl.org/dc/elements/1.1/";
+    
+    /**
+     * The dublin core terms namespace. 
+     */
+    String DCTERMS_NAMESPACE = "http://purl.org/dc/terms/";
+    
+    /**
+     * A name given to the resource. Typically, Title will be a name by which the resource is
+     * formally known.
+     */
+    static final String ELEMENT_TITLE = "title";
+
+    /**
+     * An entity primarily responsible for making the content of the resource. Examples of Creator
+     * include a person, an organization, or a service. Typically, the name of a Creator should be
+     * used to indicate the entity.
+     */
+    static final String ELEMENT_CREATOR = "creator";
+
+    /**
+     * A topic of the content of the resource. Typically, Subject will be expressed as keywords, key
+     * phrases or classification codes that describe a topic of the resource. Recommended best
+     * practice is to select a value from a controlled vocabulary or formal classification scheme.
+     */
+    static final String ELEMENT_SUBJECT = "subject";
+
+    /**
+     * An account of the content of the resource. Examples of Description include, but is not
+     * limited to: an abstract, table of contents, reference to a graphical representation of
+     * content or a free-text account of the content.
+     */
+    static final String ELEMENT_DESCRIPTION = "description";
+
+    /**
+     * An entity responsible for making the resource available. Examples of Publisher include a
+     * person, an organization, or a service. Typically, the name of a Publisher should be used to
+     * indicate the entity.
+     */
+    static final String ELEMENT_PUBLISHER = "publisher";
+
+    /**
+     * An entity responsible for making contributions to the content of the resource. Examples of
+     * Contributor include a person, an organization, or a service. Typically, the name of a
+     * Contributor should be used to indicate the entity.
+     */
+    static final String ELEMENT_CONTRIBUTOR = "contributor";
+
+    /**
+     * A date of an event in the lifecycle of the resource. Typically, Date will be associated with
+     * the creation or availability of the resource. Recommended best practice for encoding the date
+     * value is defined in a profile of ISO 8601 [W3CDTF] and includes (among others) dates of the
+     * form YYYY-MM-DD.
+     */
+    static final String ELEMENT_DATE = "date";
+
+    /**
+     * The nature or genre of the content of the resource. Type includes terms describing general
+     * categories, functions, genres, or aggregation levels for content. Recommended best practice
+     * is to select a value from a controlled vocabulary (for example, the DCMI Type Vocabulary
+     * [DCT1]). To describe the physical or digital manifestation of the resource, use the FORMAT
+     * element.
+     */
+    static final String ELEMENT_TYPE = "type";
+
+    /**
+     * The physical or digital manifestation of the resource. Typically, Format may include the
+     * media-type or dimensions of the resource. Format may be used to identify the software,
+     * hardware, or other equipment needed to display or operate the resource. Examples of
+     * dimensions include size and duration. Recommended best practice is to select a value from a
+     * controlled vocabulary (for example, the list of Internet Media Types [MIME] defining computer
+     * media formats).
+     */
+    static final String ELEMENT_FORMAT = "format";
+
+    /**
+     * An unambiguous reference to the resource within a given context. Recommended best practice is
+     * to identify the resource by means of a string or number conforming to a formal identification
+     * system. Formal identification systems include but are not limited to the Uniform Resource
+     * Identifier (URI) (including the Uniform Resource Locator (URL)), the Digital Object
+     * Identifier (DOI) and the International Standard Book Number (ISBN).
+     */
+    static final String ELEMENT_IDENTIFIER = "identifier";
+
+    /**
+     * A Reference to a resource from which the present resource is derived. The present resource
+     * may be derived from the Source resource in whole or in part. Recommended best practice is to
+     * identify the referenced resource by means of a string or number conforming to a formal
+     * identification system.
+     */
+    static final String ELEMENT_SOURCE = "source";
+
+    /**
+     * A language of the intellectual content of the resource. Recommended best practice is to use
+     * RFC 3066 [RFC3066] which, in conjunction with ISO639 [ISO639]), defines two- and three-letter
+     * primary language tags with optional subtags. Examples include "en" or "eng" for English,
+     * "akk" for Akkadian", and "en-GB" for English used in the United Kingdom.
+     */
+    static final String ELEMENT_LANGUAGE = "language";
+
+    /**
+     * A reference to a related resource. Recommended best practice is to identify the referenced
+     * resource by means of a string or number conforming to a formal identification system.
+     */
+    static final String ELEMENT_RELATION = "relation";
+
+    /**
+     * The extent or scope of the content of the resource. Typically, Coverage will include spatial
+     * location (a place name or geographic coordinates), temporal period (a period label, date, or
+     * date range) or jurisdiction (such as a named administrative entity). Recommended best
+     * practice is to select a value from a controlled vocabulary (for example, the Thesaurus of
+     * Geographic Names [TGN]) and to use, where appropriate, named places or time periods in
+     * preference to numeric identifiers such as sets of coordinates or date ranges.
+     */
+    static final String ELEMENT_COVERAGE = "coverage";
+
+    /**
+     * Information about rights held in and over the resource. Typically, Rights will contain a
+     * rights management statement for the resource, or reference a service providing such
+     * information. Rights information often encompasses Intellectual Property Rights (IPR),
+     * Copyright, and various Property Rights. If the Rights element is absent, no assumptions may
+     * be made about any rights held in or over the resource.
+     */
+    static final String ELEMENT_RIGHTS = "rights";
+
+    /**
+     * A summary of the content of the resource.
+     */
+    static final String TERM_ABSTRACT = "abstract";
+
+    /**
+     * Information about who can access the resource or an indication of its security status. Access
+     * Rights may include information regarding access or restrictions based on privacy, security or
+     * other regulations.
+     */
+    static final String TERM_ACCESSRIGHTS = "accessRights";
+
+    /**
+     * Any form of the title used as a substitute or alternative to the formal title of the
+     * resource. This qualifier can include Title abbreviations as well as translations.
+     */
+    static final String TERM_ALTERNATIVE = "alternative";
+
+    /**
+     * A class of entity for whom the resource is intended or useful. A class of entity may be
+     * determined by the creator or the publisher or by a third party.
+     */
+    static final String TERM_AUDIENCE = "audience";
+
+    /**
+     * Date (often a range) that the resource will become or did become available.
+     */
+    static final String TERM_AVAILABLE = "available";
+
+    /**
+     * A bibliographic reference for the resource. Recommended practice is to include sufficient
+     * bibliographic detail to identify the resource as unambiguously as possible, whether or not
+     * the citation is in a standard form.
+     */
+    static final String TERM_BIBLIOGRAPHICCITATION = "bibliographicCitation";
+
+    /**
+     * A reference to an established standard to which the resource conforms.
+     */
+    static final String TERM_CONFORMSTO = "conformsTo";
+
+    /**
+     * Date of creation of the resource.
+     */
+    static final String TERM_CREATED = "created";
+
+    /**
+     * Date of acceptance of the resource (e.g. of thesis by university department, of article by
+     * journal, etc.).
+     */
+    static final String TERM_DATEACCEPTED = "dateAccepted";
+
+    /**
+     * Date of a statement of copyright.
+     */
+    static final String TERM_DATECOPYRIGHTED = "dateCopyrighted";
+
+    /**
+     * Date of submission of the resource (e.g. thesis, articles, etc.).
+     */
+    static final String TERM_DATESUBMITTED = "dateSubmitted";
+
+    /**
+     * A general statement describing the education or training context. Alternatively, a more
+     * specific statement of the location of the audience in terms of its progression through an
+     * education or training context.
+     */
+    static final String TERM_EDUCATIONLEVEL = "educationLevel";
+
+    /**
+     * The size or duration of the resource.
+     */
+    static final String TERM_EXTENT = "extent";
+
+    /**
+     * The described resource pre-existed the referenced resource, which is essentially the same
+     * intellectual content presented in another format.
+     */
+    static final String TERM_HASFORMAT = "hasFormat";
+
+    /**
+     * The described resource includes the referenced resource either physically or logically.
+     */
+    static final String TERM_HASPART = "hasPart";
+
+    /**
+     * The described resource has a version, edition, or adaptation, namely, the referenced
+     * resource.
+     */
+    static final String TERM_HASVERSION = "hasVersion";
+
+    /**
+     * The described resource is the same intellectual content of the referenced resource, but
+     * presented in another format.
+     */
+    static final String TERM_ISFORMATOF = "isFormatOf";
+
+    /**
+     * The described resource is a physical or logical part of the referenced resource.
+     */
+    static final String TERM_ISPARTOF = "isPartOf";
+
+    /**
+     * The described resource is referenced, cited, or otherwise pointed to by the referenced
+     * resource.
+     */
+    static final String TERM_ISREFERENCEDBY = "isReferencedBy";
+
+    /**
+     * The described resource is supplanted, displaced, or superseded by the referenced resource.
+     */
+    static final String TERM_ISREPLACEDBY = "isReplacedBy";
+
+    /**
+     * The described resource is required by the referenced resource, either physically or
+     * logically.
+     */
+    static final String TERM_ISREQUIREDBY = "isRequiredBy";
+
+    /**
+     * Date of formal issuance (e.g., publication) of the resource.
+     */
+    static final String TERM_ISSUED = "issued";
+
+    /**
+     * The described resource is a version, edition, or adaptation of the referenced resource.
+     * Changes in version imply substantive changes in content rather than differences in format.
+     */
+    static final String TERM_ISVERSIONOF = "isVersionOf";
+
+    /**
+     * A legal document giving official permission to do something with the resource. Recommended
+     * best practice is to identify the license using a URI. Examples of such licenses can be found
+     * at http://creativecommons.org/licenses/.
+     */
+    static final String TERM_LICENSE = "license";
+
+    /**
+     * A class of entity that mediates access to the resource and for whom the resource is intended
+     * or useful. The audiences for a resource are of two basic classes: (1) an ultimate beneficiary
+     * of the resource, and (2) frequently, an entity that mediates access to the resource. The
+     * mediator element refinement represents the second of these two classes.
+     */
+    static final String TERM_MEDIATOR = "mediator";
+
+    /**
+     * The material or physical carrier of the resource.
+     */
+    static final String TERM_MEDIUM = "medium";
+
+    /**
+     * Date on which the resource was changed.
+     */
+    static final String TERM_MODIFIED = "modified";
+
+    /**
+     * The described resource references, cites, or otherwise points to the referenced resource.
+     */
+    static final String TERM_REFERENCES = "references";
+
+    /**
+     * The described resource supplants, displaces, or supersedes the referenced resource.
+     */
+    static final String TERM_REPLACES = "replaces";
+
+    /**
+     * The described resource requires the referenced resource to support its function, delivery, or
+     * coherence of content.
+     */
+    static final String TERM_REQUIRES = "requires";
+
+    /**
+     * A person or organization owning or managing rights over the resource. Recommended best
+     * practice is to use the URI or name of the Rights Holder to indicate the entity.
+     */
+    static final String TERM_RIGHTSHOLDER = "rightsHolder";
+
+    /**
+     * Spatial characteristics of the intellectual content of the resource.
+     */
+    static final String TERM_SPATIAL = "spatial";
+
+    /**
+     * A list of subunits of the content of the resource.
+     */
+    static final String TERM_TABLEOFCONTENTS = "tableOfContents";
+
+    /**
+     * Temporal characteristics of the intellectual content of the resource.
+     */
+    static final String TERM_TEMPORAL = "temporal";
+
+    /**
+     * Date (often a range) of validity of a resource.
+     */
+    static final String TERM_VALID = "valid";
+
+}
diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCoreHelper.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCoreHelper.java
new file mode 100644
index 0000000..deb9fe6
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/dublincore/DublinCoreHelper.java
@@ -0,0 +1,54 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ *  contributor license agreements.  See the NOTICE file distributed with

+ *  this work for additional information regarding copyright ownership.

+ *  The ASF licenses this file to You under the Apache License, Version 2.0

+ *  (the "License"); you may not use this file except in compliance with

+ *  the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ *  Unless required by applicable law or agreed to in writing, software

+ *  distributed under the License is distributed on an "AS IS" BASIS,

+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ *  See the License for the specific language governing permissions and

+ *  limitations under the License.

+ *

+ */

+package org.apache.lenya.cms.metadata.dublincore;

+

+import org.apache.lenya.cms.metadata.MetaData;

+import org.apache.lenya.cms.metadata.MetaDataException;

+import org.apache.lenya.cms.metadata.MetaDataOwner;

+import org.apache.lenya.cms.publication.Document;

+

+/**

+ * Helper class to access dublin core meta data.

+ */

+public class DublinCoreHelper {

+

+    /**

+     * @param owner The owner.

+     * @return The dublin core title or <code>null</code> if the title is not set.

+     * @throws MetaDataException if the owner has no dublin core meta data.

+     */

+    public static String getTitle(MetaDataOwner owner) throws MetaDataException {

+        return getDublinCore(owner).getFirstValue(DublinCore.ELEMENT_TITLE);

+    }

+    

+    /**

+     * @param doc The document.

+     * @param fallbackToUuid If the dublin core title is <code>null</code>, the document's UUID is returned.

+     * @return The dublin core title.

+     * @throws MetaDataException if the document has no dublin core meta data.

+     */

+    public static String getTitle(Document doc, boolean fallbackToUuid) throws MetaDataException {

+        String title = DublinCoreHelper.getTitle(doc);

+        return title == null ? doc.getUUID() : title;

+    }

+    

+    protected static MetaData getDublinCore(MetaDataOwner owner) throws MetaDataException {

+        return owner.getMetaData(DublinCore.DC_NAMESPACE);

+    }

+    

+}

diff --git a/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/usecases/Metadata.java b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/usecases/Metadata.java
new file mode 100644
index 0000000..fddbff6
--- /dev/null
+++ b/org.apache.lenya.core.metadata/src/main/java/org/apache/lenya/cms/metadata/usecases/Metadata.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ * 
+ */
+package org.apache.lenya.cms.metadata.usecases;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lenya.cms.metadata.Element;
+import org.apache.lenya.cms.metadata.MetaData;
+import org.apache.lenya.cms.metadata.MetaDataRegistry;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Node;
+import org.apache.lenya.cms.site.usecases.SiteUsecase;
+import org.apache.lenya.cms.usecase.UsecaseException;
+import org.apache.lenya.cms.workflow.WorkflowUtil;
+
+/**
+ * Usecase to edit metadata for a resource.
+ * 
+ * @version $Id$
+ */
+public class Metadata extends SiteUsecase {
+    
+    private MetaDataRegistry metaDataRegistry;
+
+    /**
+     * @see org.apache.lenya.cms.usecase.AbstractUsecase#getNodesToLock()
+     */
+    protected Node[] getNodesToLock() throws UsecaseException {
+        Node[] objects = new Node[0];
+        if(getSourceDocument() != null) {
+            objects = new Node[] { getSourceDocument() };
+        }
+        return objects;
+    }
+    
+    /**
+     * Object to pass a meta data entry to the view.
+     */
+    public static class MetaDataWrapper {
+        
+        private String[] values;
+        private Element element;
+        private boolean editable;
+        
+        /**
+         * @param element The element.
+         * @param values The values for the element.
+         * @param canChange If the element value can be changed via the GUI. A <code>true</code>
+         *     value is only effective if the element itself is editable.
+         */
+        public MetaDataWrapper(Element element, String[] values, boolean canChange) {
+            this.values = values;
+            this.element = element;
+            this.editable = element.isEditable() && canChange;
+        }
+        
+        /**
+         * @return The values for the element.
+         */
+        public String[] getValues() {
+            return this.values;
+        }
+        
+        /**
+         * @return The element.
+         */
+        public Element getElement() {
+            return this.element;
+        }
+        
+        /**
+         * @return If the value can be changed via the GUI.
+         */
+        public boolean isEditable() {
+            return this.editable;
+        }
+        
+    }
+
+    /**
+     * @see org.apache.lenya.cms.usecase.AbstractUsecase#initParameters()
+     */
+    protected void initParameters() {
+        super.initParameters();
+        
+        Document doc = getSourceDocument();
+        if (doc == null) {
+            return;
+        }
+        
+
+        try {
+            boolean canChange = WorkflowUtil.canInvoke(doc, "edit");
+            
+            if (!canChange) {
+                addInfoMessage("cannot-change-metadata");
+            }
+            
+            List numbers = new ArrayList();
+            Map num2namespace = new HashMap();
+            List keyList = new ArrayList();
+
+            String[] namespaces = getMetaDataRegistry().getNamespaceUris();
+
+            for (int nsIndex = 0; nsIndex < namespaces.length; nsIndex++) {
+                MetaData meta = doc.getMetaData(namespaces[nsIndex]);
+                String[] keys = meta.getPossibleKeys();
+                for (int keyIndex = 0; keyIndex < keys.length; keyIndex++) {
+                    String key = "ns" + nsIndex + "." + keys[keyIndex];
+                    String[] values = meta.getValues(keys[keyIndex]);
+                    Element element = meta.getElementSet().getElement(keys[keyIndex]);
+                    setParameter(key, new MetaDataWrapper(element, values, canChange));
+                    keyList.add(key);
+                }
+                numbers.add("" + nsIndex);
+                num2namespace.put("" + nsIndex, namespaces[nsIndex]);
+            }
+
+            setParameter("numbers", numbers);
+            setParameter("namespaces", num2namespace);
+
+            Collections.sort(keyList);
+            setParameter("keys", keyList);
+
+        } catch (Exception e) {
+            getLogger().error("Unable to load meta data.", e);
+            addErrorMessage("Unable to load meta data: " + e.getMessage());
+        }
+    }
+
+    protected MetaDataRegistry getMetaDataRegistry() {
+        return metaDataRegistry;
+    }
+
+    public void setMetaDataRegistry(MetaDataRegistry metaDataRegistry) {
+        this.metaDataRegistry = metaDataRegistry;
+    }
+    
+}