Index: src/test/java/org/apache/tika/metadata/TestMetadata.java
===================================================================
--- src/test/java/org/apache/tika/metadata/TestMetadata.java	(revision 1339327)
+++ src/test/java/org/apache/tika/metadata/TestMetadata.java	(working copy)
@@ -330,4 +330,14 @@
         		"1970-01-01T00:00:01", meta.get(Metadata.DATE));
     }
     
+    public void testCompositeProperty() {
+    	Metadata meta = new Metadata();
+    	Property compositeProperty = Property.composite(
+            DublinCore.DESCRIPTION, new Property[] { Property.internalText(Metadata.DESCRIPTION)});
+    	String message = "composite description";
+    	meta.set(compositeProperty, message);
+    	assertEquals(message, meta.get(DublinCore.DESCRIPTION));
+    	assertEquals(message, meta.get(Metadata.DESCRIPTION));
+    }
+    
 }
Index: src/main/java/org/apache/tika/metadata/PropertyTypeException.java
===================================================================
--- src/main/java/org/apache/tika/metadata/PropertyTypeException.java	(revision 1339327)
+++ src/main/java/org/apache/tika/metadata/PropertyTypeException.java	(working copy)
@@ -34,4 +34,9 @@
     public PropertyTypeException(ValueType expected, ValueType found) {
         super("Expected a property with a " + expected + " value, but received a " + found);
     }
+    public PropertyTypeException(PropertyType unsupportedPropertyType) {
+    	super((unsupportedPropertyType != PropertyType.COMPOSITE) ? 
+    			(unsupportedPropertyType + " is not supported") : 
+    			("composite properties must not include other composite properties as primary or secondary"));
+    }
 }
Index: src/main/java/org/apache/tika/metadata/Metadata.java
===================================================================
--- src/main/java/org/apache/tika/metadata/Metadata.java	(revision 1339351)
+++ src/main/java/org/apache/tika/metadata/Metadata.java	(working copy)
@@ -31,6 +31,8 @@
 import java.util.Properties;
 import java.util.TimeZone;
 
+import org.apache.tika.metadata.Property.PropertyType;
+
 /**
  * A multi-valued metadata container.
  */
@@ -349,7 +351,19 @@
      * @param value    property value
      */
     public void set(Property property, String value) {
-        set(property.getName(), value);
+        if (property == null) {
+            throw new NullPointerException("property must not be null");
+        }
+        if (property.getPropertyType() == PropertyType.COMPOSITE) {
+            set(property.getPrimaryProperty(), value);
+            if (property.getSecondaryExtractProperties() != null) {
+                for (Property secondaryExtractProperty : property.getSecondaryExtractProperties()) {
+                    set(secondaryExtractProperty, value);
+                }
+            }
+        } else {
+            set(property.getName(), value);
+        }
     }
 
     /**
Index: src/main/java/org/apache/tika/metadata/Property.java
===================================================================
--- src/main/java/org/apache/tika/metadata/Property.java	(revision 1339327)
+++ src/main/java/org/apache/tika/metadata/Property.java	(working copy)
@@ -37,12 +37,12 @@
 public final class Property implements Comparable<Property> {
 
     public static enum PropertyType {
-        SIMPLE, STRUCTURE, BAG, SEQ, ALT
+        SIMPLE, STRUCTURE, BAG, SEQ, ALT, COMPOSITE
     }
 
     public static enum ValueType {
         BOOLEAN, OPEN_CHOICE, CLOSED_CHOICE, DATE, INTEGER, LOCALE,
-        MIME_TYPE, PROPER_NAME, RATIONAL, REAL, TEXT, URI, URL, XPATH
+        MIME_TYPE, PROPER_NAME, RATIONAL, REAL, TEXT, URI, URL, XPATH, PROPERTY
     }
 
     private static final Map<String, Property> properties =
@@ -55,6 +55,10 @@
     private final PropertyType propertyType;
 
     private final ValueType valueType;
+    
+    private final Property primaryProperty;
+    
+    private final Property[] secondaryExtractProperties;
 
     /**
      * The available choices for the open and closed choice value types.
@@ -63,7 +67,7 @@
 
     private Property(
             String name, boolean internal, PropertyType propertyType,
-            ValueType valueType, String[] choices) {
+            ValueType valueType, String[] choices, Property primaryProperty, Property[] secondaryExtractProperties) {
         this.name = name;
         this.internal = internal;
         this.propertyType = propertyType;
@@ -74,11 +78,19 @@
         } else {
             this.choices = null;
         }
+        this.primaryProperty = primaryProperty;
+        this.secondaryExtractProperties = secondaryExtractProperties;
 
         synchronized (properties) {
             properties.put(name, this);
         }
     }
+    
+    private Property(
+            String name, boolean internal, PropertyType propertyType,
+            ValueType valueType, String[] choices) {
+    	this(name, internal, propertyType, valueType, choices, null, null);
+    }
 
     private Property(
             String name, boolean internal,
@@ -95,7 +107,7 @@
             PropertyType propertyType, ValueType valueType) {
         this(name, internal, propertyType, valueType, null);
     }
-
+    
     public String getName() {
         return name;
     }
@@ -126,7 +138,29 @@
     public Set<String> getChoices() {
         return choices;
     }
+    
+    /**
+     * Gets the primary property for a composite property
+     * 
+     * @return the primary property
+     */
+    public Property getPrimaryProperty() {
+    	if (primaryProperty != null) {
+    		return primaryProperty;
+    	} else {
+    		return this;
+    	}
+    }
 
+    /**
+     * Gets the secondary properties for a composite property
+     * 
+     * @return the secondary properties
+     */
+    public Property[] getSecondaryExtractProperties() {
+		return secondaryExtractProperties;
+	}
+    
     public static SortedSet<Property> getProperties(String prefix) {
         SortedSet<Property> set = new TreeSet<Property>();
         String p = prefix + ":";
@@ -209,6 +243,41 @@
     public static Property externalText(String name) {
         return new Property(name, false, ValueType.TEXT);
     }
+    
+    /**
+     * Constructs a new composite property from the given primary and array of secondary properties.
+     * <p>
+     * Note that name of the composite property is taken from its primary property, 
+     * and primary and secondary properties must not be composite properties themselves.
+     * 
+     * @param primaryProperty
+     * @param secondaryExtractProperties
+     * @return the composite property
+     */
+    public static Property composite(Property primaryProperty, Property[] secondaryExtractProperties) {
+        if (primaryProperty == null) {
+            throw new NullPointerException("primaryProperty must not be null");
+        }
+        if (primaryProperty.getPropertyType() == PropertyType.COMPOSITE) {
+            throw new PropertyTypeException(primaryProperty.getPropertyType());
+        }
+        if (secondaryExtractProperties != null) {
+            for (Property secondaryExtractProperty : secondaryExtractProperties) {
+                if (secondaryExtractProperty.getPropertyType() == PropertyType.COMPOSITE) {
+                    throw new PropertyTypeException(secondaryExtractProperty.getPropertyType());
+                }
+            }
+        }
+        String[] choices = null;
+        if (primaryProperty.getChoices() != null) {
+            choices = primaryProperty.getChoices().toArray(
+                    new String[primaryProperty.getChoices().size()]);
+        }
+        return new Property(primaryProperty.getName(),
+                primaryProperty.isInternal(), PropertyType.COMPOSITE,
+                ValueType.PROPERTY, choices, primaryProperty,
+                secondaryExtractProperties);
+    }
 
     //----------------------------------------------------------< Comparable >
 
