Polishing interfaces and builders for the content item api
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Artifact.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Artifact.java
index b4c6d6c..71ef83e 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Artifact.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Artifact.java
@@ -19,43 +19,31 @@
  * under the License.
  */
 
-import org.apache.archiva.repository.storage.StorageAsset;
-
 /**
- *
  * Represents a artifact of a repository. This object contains unique coordinates of the
  * artifact. A artifact has exactly one file representation in the repository.
  * The artifact instance does not tell, if the file exists or is readable. It just
  * keeps the coordinates and some meta information of the artifact.
- *
+ * <p>
  * Artifact implementations should be immutable. The implementation must not always represent the current state of the
  * corresponding storage asset (file). It is just a view of the attributes for a given point in time.
- *
+ * <p>
  * Implementations must provide proper hash and equals methods.
  *
- *
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 public interface Artifact extends ContentItem
 {
-    /**
-     * The namespace is the location of the artifact.
-     * E.g. for maven artifacts it is the groupId.
-     * The namespace may be empty. Which means that is the base or root namespace.
-     *
-     * @return the namespace of the artifact. Never returns <code>null</code>.
-     */
-    String getNamespace();
 
     /**
      * The artifact identifier. The ID is unique in a given namespace of a given repository.
      * But there may exist artifacts with the same ID but different types, classifiers or extensions.
-     *
+     * <p>
      * Never returns <code>null</code> or a empty string.
      *
      * @return the identifier of the artifact. Never returns <code>null</code> or empty string
      */
-    String getId();
+    String getId( );
 
     /**
      * The version string of the artifact. The version string is exactly the version that is attached
@@ -67,13 +55,14 @@
      * @return the artifact version string
      * @see #getVersion()
      */
-    String getArtifactVersion();
+    String getArtifactVersion( );
 
     /**
      * Returns the attached version this artifact is part of.
+     *
      * @return the version object
      */
-    Version getVersion();
+    Version getVersion( );
 
     /**
      * Returns the type of the artifact. The type is some hint about the usage of the artifact.
@@ -81,7 +70,7 @@
      *
      * @return the type of the artifact. Returns never <code>null</code>, but may be empty string
      */
-    String getType();
+    String getType( );
 
     /**
      * A classifier that distinguishes artifacts.
@@ -89,13 +78,15 @@
      *
      * @return the classifier of the artifact. Returns never <code>null</code>, but may be empty string
      */
-    String getClassifier();
+    String getClassifier( );
 
     /**
      * Short cut for the file name. Should always return the same value as the artifact name.
+     *
      * @return the name of the file
      */
-    default String getFileName() {
+    default String getFileName( )
+    {
         return getAsset( ).getName( );
     }
 
@@ -105,12 +96,16 @@
      *
      * @return the file name extension
      */
-    default String getExtension() {
-        final String name = getAsset().getName();
+    default String getExtension( )
+    {
+        final String name = getAsset( ).getName( );
         final int idx = name.lastIndexOf( '.' );
-        if (idx>=0) {
+        if ( idx >= 0 )
+        {
             return name.substring( idx );
-        } else {
+        }
+        else
+        {
             return "";
         }
     }
@@ -121,21 +116,14 @@
      *
      * @return the file name remainder
      */
-    String getRemainder();
+    String getRemainder( );
 
     /**
      * Should return the mime type of the artifact.
      *
      * @return the mime type of the artifact.
      */
-    String getContentType();
-
-    /**
-     * Returns the storage representation of the artifact. The asset must not exist.
-     *
-     * @return the asset this artifact corresponds to.
-     */
-    StorageAsset getAsset();
+    String getContentType( );
 
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ContentItem.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ContentItem.java
index ec9fca6..1b6a492 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ContentItem.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ContentItem.java
@@ -19,7 +19,9 @@
  * under the License.
  */
 
+import org.apache.archiva.repository.ManagedRepositoryContent;
 import org.apache.archiva.repository.UnsupportedConversionException;
+import org.apache.archiva.repository.storage.StorageAsset;
 
 import java.util.Map;
 
@@ -27,22 +29,25 @@
 {
     /**
      * Returns the repository type specific implementation
+     *
      * @param clazz the specific implementation class
-     * @param <T> the class or interface
+     * @param <T>   the class or interface
      * @return the specific project implementation
      */
     <T extends Project> T adapt( Class<T> clazz ) throws UnsupportedConversionException;
 
     /**
      * Returns <code>true</code>, if this project supports the given adaptor class.
+     *
      * @param clazz the class to convert this project to
-     * @param <T> the type
+     * @param <T>   the type
      * @return <code>true/code>, if the implementation is supported, otherwise false
      */
     <T extends Project> boolean supports( Class<T> clazz );
 
     /**
      * Additional attributes
+     *
      * @return the additional attributes
      */
     Map<String, String> getAttributes( );
@@ -52,7 +57,22 @@
      *
      * @param key the attribute key
      * @return the value, if the key exists, otherwise <code>null</code>
-     *
      */
     String getAttribute( String key );
+
+    /**
+     * Returns the storage representation of the artifact. The asset must not exist.
+     *
+     * @return the asset this artifact corresponds to.
+     */
+    StorageAsset getAsset( );
+
+
+    /**
+     * The repository this project is part of.
+     *
+     * @return the repository content
+     */
+    ManagedRepositoryContent getRepository( );
+
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemNotFoundException.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemNotFoundException.java
new file mode 100644
index 0000000..ad87390
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemNotFoundException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.archiva.repository.content;
+
+/**
+ * This exception is thrown, if the artifact, version or group item with the selected coordinates
+ * cannot be found.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ * @since 3.0
+ */
+public class ItemNotFoundException extends Exception
+{
+    public ItemNotFoundException( )
+    {
+    }
+
+    public ItemNotFoundException( String message )
+    {
+        super( message );
+    }
+
+    public ItemNotFoundException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+
+    public ItemNotFoundException( Throwable cause )
+    {
+        super( cause );
+    }
+
+    public ItemNotFoundException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace )
+    {
+        super( message, cause, enableSuppression, writableStackTrace );
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
index 9e50c24..92f1456 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
@@ -29,51 +29,62 @@
 public interface ItemSelector
 {
 
-    String getProjectId();
+    String getProjectId( );
 
-    String getNamespace();
+    String getNamespace( );
 
     String getVersion( );
 
-    String getArtifactVersion();
+    String getArtifactVersion( );
 
     String getArtifactId( );
 
-    String getType();
+    String getType( );
 
-    String getClassifier();
+    String getClassifier( );
 
     String getAttribute( String key );
 
+    String getExtension( String extension );
+
     Map<String, String> getAttributes( );
 
-    default boolean hasNamespace() {
+    default boolean hasNamespace( )
+    {
         return !StringUtils.isEmpty( getNamespace( ) );
     }
 
-    default boolean hasProjectId() {
+    default boolean hasProjectId( )
+    {
         return !StringUtils.isEmpty( getProjectId( ) );
     }
 
-    default boolean hasVersion() {
-        return !StringUtils.isEmpty(getVersion());
+    default boolean hasVersion( )
+    {
+        return !StringUtils.isEmpty( getVersion( ) );
     }
 
-    default boolean hasArtifactId() {
+    default boolean hasArtifactId( )
+    {
         return !StringUtils.isEmpty( getArtifactId( ) );
     }
 
-    default boolean hasArtifactVersion() {
+    default boolean hasArtifactVersion( )
+    {
         return !StringUtils.isEmpty( getArtifactVersion( ) );
     }
 
-    default boolean hasType() {
+    default boolean hasType( )
+    {
         return !StringUtils.isEmpty( getType( ) );
     }
 
-    default boolean hasClassifier() {
+    default boolean hasClassifier( )
+    {
         return !StringUtils.isEmpty( getClassifier( ) );
     }
 
-    boolean hasAttributes();
+    boolean hasAttributes( );
+
+    boolean hasExtension( );
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Namespace.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Namespace.java
new file mode 100644
index 0000000..4325584
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Namespace.java
@@ -0,0 +1,35 @@
+/*
+ * 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.archiva.repository.content;
+
+import java.util.List;
+
+/**
+ * The namespace represents some kind of hierarchical coordinate where artifacts are stored.
+ * The syntax of the namespace (e.g. the separator like '.' or '/') is dependent on the repository type.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface Namespace extends ContentItem
+{
+    String getNamespace( );
+
+    List<String> getNamespacePath( );
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/PathParser.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/PathParser.java
index 3133c07..f72ca2b 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/PathParser.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/PathParser.java
@@ -24,8 +24,6 @@
 
 /**
  * PathParser interface.
- *
- *
  */
 public interface PathParser
 {
@@ -43,11 +41,12 @@
 
     /**
      * Return a item selector for the given path.
+     *
      * @param path the path relative to the repository
      * @return a item selector instance
      * @throws LayoutException if the path does not reference a valid item
      */
-    ItemSelector toItemSelector(String path) throws LayoutException;
+    ItemSelector toItemSelector( String path ) throws LayoutException;
 
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Project.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Project.java
index ef72496..4de6bad 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Project.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Project.java
@@ -19,11 +19,7 @@
  * under the License.
  */
 
-import org.apache.archiva.repository.RepositoryContent;
-import org.apache.archiva.repository.storage.StorageAsset;
-
 /**
- *
  * The project is the container for several versions each with different artifacts.
  *
  * <pre>
@@ -33,7 +29,7 @@
  *         |
  *         +--> version 2 ----> artifact 3
  * </pre>
- *
+ * <p>
  * Implementations must provide proper hash and equals methods.
  *
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -43,29 +39,17 @@
 
     /**
      * The namespace of the project
+     *
      * @return the namespace
      */
-    String getNamespace();
+    Namespace getNamespace( );
 
     /**
      * The id of the project
+     *
      * @return the project id
      */
-    String getId();
+    String getId( );
 
-    /**
-     * The repository this project is part of.
-     * @return the repository content
-     */
-    RepositoryContent getRepository();
-
-    /**
-     * Returns the asset that corresponds to this project.
-     * It depends of the implementation, if the asset provides additional information about the
-     * project or not.
-     *
-     * @return the repository asset that represents this project
-     */
-    StorageAsset getAsset();
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Version.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Version.java
index 0114939..071ef5b 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Version.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/Version.java
@@ -19,15 +19,11 @@
  * under the License.
  */
 
-import org.apache.archiva.repository.UnsupportedRepositoryTypeException;
-import org.apache.archiva.repository.storage.StorageAsset;
-
-import java.util.Map;
+import java.util.List;
 
 /**
- *
  * Each artifact is attached to exactly one version.
- *
+ * <p>
  * Implementations must provide proper hash and equals methods.
  *
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -39,21 +35,20 @@
      *
      * @return the version string
      */
-    String getVersion();
+    String getVersion( );
 
     /**
-     * Returns the local representation of the version. For maven this is a directory.
-     * It is implementation dependent, what exactly this asset points to.
+     * Returns the version segments. E.g. for 1.3.4 it will return ["1","3"."4"]
      *
-     * @return the local storage representation of the version
+     * @return
      */
-    StorageAsset getAsset();
+    List<String> getVersionSegments( );
 
     /**
-     * Each version is attached to a project.
-     * @return the attached project
+     * Returns the project this version is attached to.
+     *
+     * @return the project instance. Will never return <code>null</code>
      */
-    Project getProject();
-
+    Project getProject( );
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaArtifact.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaArtifact.java
index 7ac99a7..5d28e83 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaArtifact.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaArtifact.java
@@ -21,18 +21,34 @@
 
 import org.apache.archiva.repository.content.Artifact;
 import org.apache.archiva.repository.content.Version;
+import org.apache.archiva.repository.content.base.builder.ArtifactOptBuilder;
+import org.apache.archiva.repository.content.base.builder.ArtifactVersionBuilder;
+import org.apache.archiva.repository.content.base.builder.ArtifactWithIdBuilder;
+import org.apache.archiva.repository.content.base.builder.WithVersionObjectBuilder;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
 
 /**
- *
- * Base implementation of artifact. A builder is used to create instances.
+ * Base implementation of artifact.
+ * <p>
+ * You have to use the builder method {@link #withAsset(StorageAsset)} to create a instance.
+ * The build() method can be called after the required attributes are set.
+ * <p>
+ * Artifact are equal if the following coordinates match:
+ * <ul>
+ *     <li>repository</li>
+ *     <li>asset</li>
+ *     <li>version</li>
+ *     <li>artifactId</li>
+ *     <li>artifactVersion</li>
+ *     <li>type</li>
+ *     <li>classifier</li>
+ * </ul>
  *
  * @author Martin Stockhammer <martin_s@apache.org>
  */
 public class ArchivaArtifact extends ArchivaContentItem implements Artifact
 {
-    private String namespace;
     private String id;
     private String artifactVersion;
     private Version version;
@@ -40,18 +56,13 @@
     private String classifier;
     private String remainder;
     private String contentType;
-    private StorageAsset asset;
 
-    private ArchivaArtifact() {
-
-    }
-
-    @Override
-    public String getNamespace( )
+    private ArchivaArtifact( )
     {
-        return namespace;
+
     }
 
+
     @Override
     public String getId( )
     {
@@ -94,107 +105,159 @@
         return contentType;
     }
 
+
+    /**
+     * Returns the builder for creating a new artifact instance. You have to fill the
+     * required attributes before the build() method is available.
+     *
+     * @param asset the storage asset representing the artifact
+     * @return a builder for creating new artifact instance
+     */
+    public static WithVersionObjectBuilder withAsset( StorageAsset asset )
+    {
+        return new Builder( ).withAsset( asset );
+    }
+
+
     @Override
-    public StorageAsset getAsset( )
+    public boolean equals( Object o )
     {
-        return asset;
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+        if ( !super.equals( o ) ) return false;
+
+        ArchivaArtifact that = (ArchivaArtifact) o;
+
+        if ( !id.equals( that.id ) ) return false;
+        if ( !artifactVersion.equals( that.artifactVersion ) ) return false;
+        if ( !version.equals( that.version ) ) return false;
+        if ( !type.equals( that.type ) ) return false;
+        return classifier.equals( that.classifier );
     }
 
-    public static ArtifactVersionBuilder withId(String id) {
-        return new Builder( ).withId( id );
-    }
-
-
-    public interface ArtifactVersionBuilder {
-        VersionBuilder withArtifactVersion( String version );
-    }
-    public interface VersionBuilder {
-        AssetBuilder withVersion( Version version );
-    }
-    public interface AssetBuilder {
-        Builder withAsset( StorageAsset asset );
-    }
-
-
-    public static class Builder implements ArtifactVersionBuilder, VersionBuilder,
-        AssetBuilder
+    @Override
+    public int hashCode( )
     {
-        ArchivaArtifact artifact = new ArchivaArtifact( );
+        int result = super.hashCode( );
+        result = 31 * result + id.hashCode( );
+        result = 31 * result + artifactVersion.hashCode( );
+        result = 31 * result + version.hashCode( );
+        result = 31 * result + type.hashCode( );
+        result = 31 * result + classifier.hashCode( );
+        return result;
+    }
 
-        public ArtifactVersionBuilder withId(String id) {
-            artifact.id = id;
-            return this;
+    private static class Builder
+        extends ContentItemBuilder<ArchivaArtifact, ArtifactOptBuilder, WithVersionObjectBuilder>
+        implements ArtifactVersionBuilder, WithVersionObjectBuilder, ArtifactWithIdBuilder, ArtifactOptBuilder
+    {
+
+        Builder( )
+        {
+            super( new ArchivaArtifact( ) );
         }
 
-
         @Override
-        public VersionBuilder withArtifactVersion( String version )
+        protected ArtifactOptBuilder getOptBuilder( )
         {
-            if ( StringUtils.isEmpty( version ) ) {
-                throw new IllegalArgumentException( "version may not be null or empty" );
-            }
-            artifact.artifactVersion = version;
             return this;
         }
 
         @Override
-        public AssetBuilder withVersion( Version version )
+        protected WithVersionObjectBuilder getNextBuilder( )
         {
-            if (version==null) {
+            return this;
+        }
+
+        @Override
+        public ArtifactWithIdBuilder withVersion( Version version )
+        {
+            if ( version == null )
+            {
                 throw new IllegalArgumentException( "version may not be null" );
             }
-            artifact.version = version;
+            item.version = version;
+            super.setRepository( version.getRepository( ) );
             return this;
         }
 
-        public Builder withAsset(StorageAsset asset) {
-            if (asset==null) {
-                throw new IllegalArgumentException( "Asset may not be null" );
+        @Override
+        public ArtifactOptBuilder withId( String id )
+        {
+            if ( StringUtils.isEmpty( id ) )
+            {
+                throw new IllegalArgumentException( "Artifact id may not be null or empty" );
             }
-            artifact.asset = asset;
+            item.id = id;
             return this;
         }
 
-        public Builder withNamespace(String namespace) {
-            artifact.namespace = namespace;
-            return this;
-        }
 
-        public Builder withType(String type) {
-            artifact.type = type;
-            return this;
-        }
-
-        public Builder withClassifier(String classifier) {
-            artifact.classifier = classifier;
-            return this;
-        }
-
-        public Builder withRemainder(String remainder) {
-            artifact.remainder = remainder;
-            return this;
-        }
-
-        public Builder withContentType(String contentType) {
-            artifact.contentType = contentType;
-            return this;
-        }
-
-        public ArchivaArtifact build() {
-            if (artifact.namespace==null) {
-                artifact.namespace = "";
+        @Override
+        public ArtifactOptBuilder withArtifactVersion( String version )
+        {
+            if ( version == null )
+            {
+                throw new IllegalArgumentException( "version may not be null" );
             }
-            if (artifact.classifier==null) {
-                artifact.classifier = "";
+            item.artifactVersion = version;
+            return this;
+        }
+
+        @Override
+        public ArtifactOptBuilder withType( String type )
+        {
+            item.type = type;
+            return this;
+        }
+
+        @Override
+        public ArtifactOptBuilder withClassifier( String classifier )
+        {
+            item.classifier = classifier;
+            return this;
+        }
+
+        @Override
+        public ArtifactOptBuilder withRemainder( String remainder )
+        {
+            item.remainder = remainder;
+            return this;
+        }
+
+        @Override
+        public ArtifactOptBuilder withContentType( String contentType )
+        {
+            item.contentType = contentType;
+            return this;
+        }
+
+        @Override
+        public ArchivaArtifact build( )
+        {
+            super.build( );
+            if ( item.artifactVersion == null )
+            {
+                item.artifactVersion = "";
             }
-            if (artifact.type == null)  {
-                artifact.type = "";
+            if ( item.classifier == null )
+            {
+                item.classifier = "";
             }
-            if (artifact.contentType==null) {
-                artifact.contentType = "";
+            if ( item.type == null )
+            {
+                item.type = "";
+            }
+            if ( item.contentType == null )
+            {
+                item.contentType = "";
+            }
+            if ( item.remainder == null )
+            {
+                item.remainder = "";
             }
 
-            return artifact;
+            return item;
         }
     }
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaContentItem.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaContentItem.java
index 3b89a04..58d3335 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaContentItem.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaContentItem.java
@@ -19,8 +19,13 @@
  * under the License.
  */
 
+import org.apache.archiva.repository.ManagedRepositoryContent;
 import org.apache.archiva.repository.content.ContentItem;
 import org.apache.archiva.repository.content.Project;
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.apache.archiva.repository.content.base.builder.WithAssetBuilder;
+import org.apache.archiva.repository.content.base.builder.WithRepositoryBuilder;
+import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.Collections;
@@ -29,23 +34,26 @@
 
 /**
  * Abstract implementation of ContentItem interface.
- *
+ * <p>
  * The attribute map is created, when the first values are put to the map.
- *
  */
 public abstract class ArchivaContentItem implements ContentItem
 {
 
     private Map<String, String> attributes;
+    private ManagedRepositoryContent repository;
+    private StorageAsset asset;
 
     @Override
-    public <T extends Project> T adapt( Class<T> clazz ) {
-        return (T)this;
+    public <T extends Project> T adapt( Class<T> clazz )
+    {
+        return (T) this;
     }
 
     @Override
-    public <T extends Project> boolean supports( Class<T> clazz ) {
-        return clazz != null && clazz.isAssignableFrom( this.getClass() );
+    public <T extends Project> boolean supports( Class<T> clazz )
+    {
+        return clazz != null && clazz.isAssignableFrom( this.getClass( ) );
     }
 
 
@@ -58,9 +66,11 @@
     @Override
     public Map<String, String> getAttributes( )
     {
-        if (this.attributes==null) {
+        if ( this.attributes == null )
+        {
             return Collections.emptyMap( );
-        } else
+        }
+        else
         {
             return Collections.unmodifiableMap( this.attributes );
         }
@@ -69,15 +79,18 @@
     /**
      * Adds a attribute value. The key must not be <code>null</code>.
      *
-     * @param key the attribute key
+     * @param key   the attribute key
      * @param value the attribute value
      * @throws IllegalArgumentException if the key is <code>null</code> or empty
      */
-    public void putAttribute(String key, String value) throws IllegalArgumentException {
-        if (this.attributes==null) {
+    public void putAttribute( String key, String value ) throws IllegalArgumentException
+    {
+        if ( this.attributes == null )
+        {
             this.attributes = new HashMap<>( );
         }
-        if ( StringUtils.isEmpty( key ) ) {
+        if ( StringUtils.isEmpty( key ) )
+        {
             throw new IllegalArgumentException( "Key value must not be empty or null" );
         }
         this.attributes.put( key, value );
@@ -86,11 +99,127 @@
     @Override
     public String getAttribute( String key )
     {
-        if (this.attributes==null) {
+        if ( this.attributes == null )
+        {
             return null;
-        } else
+        }
+        else
         {
             return this.attributes.get( key );
         }
     }
+
+    @Override
+    public ManagedRepositoryContent getRepository( )
+    {
+        return repository;
+    }
+
+    @Override
+    public StorageAsset getAsset( )
+    {
+        return asset;
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        ArchivaContentItem that = (ArchivaContentItem) o;
+
+        if ( !repository.equals( that.repository ) ) return false;
+        return asset.equals( that.asset );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = repository.hashCode( );
+        result = 31 * result + asset.hashCode( );
+        return result;
+    }
+
+
+    /// Builder section
+
+    /*
+     * Builder implementation for each content item.
+     * Should be extended by the subclasses.
+     */
+
+
+    /**
+     * Builder for content item. Must be extended by subclasses.
+     * The builder uses chained interfaces for building the required attributes. That means you have to set
+     * some certain attributes, before you can build the content item instance via the {@link #build()} method.
+     * <p>
+     * Subclasses should extend from this class and provide the interface/class for the destination item,
+     * a interface for the optional attributes and a interface that is returned after the last required attribute is
+     * set.
+     * <p>
+     * The interface for optional attributes should inherit from {@link OptBuilder}
+     *
+     * @param <I> the item class that should be built
+     * @param <O> the class/interface for the optional attributes
+     * @param <N> the class/interface for the next (required) attribute after the base attributes are set
+     */
+    protected abstract static class ContentItemBuilder<I extends ArchivaContentItem, O extends OptBuilder<I, O>, N>
+        implements WithRepositoryBuilder, WithAssetBuilder<N>,
+        OptBuilder<I, O>
+    {
+
+        protected I item;
+
+        protected ContentItemBuilder( I item )
+        {
+            this.item = item;
+        }
+
+        protected abstract O getOptBuilder( );
+
+        protected abstract N getNextBuilder( );
+
+        public WithAssetBuilder<N> withRepository( ManagedRepositoryContent repository )
+        {
+            if ( repository == null )
+            {
+                throw new IllegalArgumentException( "Repository may not be null" );
+            }
+            ( (ArchivaContentItem) item ).repository = repository;
+            return this;
+        }
+
+        public N withAsset( StorageAsset asset )
+        {
+            if ( asset == null )
+            {
+                throw new IllegalArgumentException( "Asset may not be null" );
+            }
+            ( (ArchivaContentItem) item ).asset = asset;
+            return getNextBuilder( );
+        }
+
+        public O withAttribute( String key, String value )
+        {
+            if ( StringUtils.isEmpty( key ) )
+            {
+                throw new IllegalArgumentException( "Attribute key may not be null" );
+            }
+            item.putAttribute( key, value );
+            return getOptBuilder( );
+        }
+
+        protected void setRepository( ManagedRepositoryContent repository )
+        {
+            ( (ArchivaContentItem) item ).repository = repository;
+        }
+
+        public I build( )
+        {
+            return item;
+        }
+
+    }
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
index 34c10ec..c66d90a 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
@@ -20,6 +20,7 @@
  */
 
 import org.apache.archiva.repository.content.ItemSelector;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -38,15 +39,18 @@
     private String namespace = "";
     private String type = null;
     private String classifier = null;
-    private Map<String,String> attributes;
+    private String extension = null;
+    private Map<String, String> attributes;
 
 
-    private ArchivaItemSelector() {
+    private ArchivaItemSelector( )
+    {
 
     }
 
-    public static Builder builder() {
-        return new Builder();
+    public static Builder builder( )
+    {
+        return new Builder( );
     }
 
     public static class Builder
@@ -108,13 +112,22 @@
             return this;
         }
 
-        public ArchivaItemSelector build() {
+        public Builder withExtension( String extension )
+        {
+            selector.extension = extension;
+            return this;
+        }
+
+        public ArchivaItemSelector build( )
+        {
             return selector;
         }
     }
 
-    private void setAttribute(String key, String value) {
-        if (this.attributes == null) {
+    private void setAttribute( String key, String value )
+    {
+        if ( this.attributes == null )
+        {
             this.attributes = new HashMap<>( );
         }
         this.attributes.put( key, value );
@@ -177,11 +190,20 @@
     }
 
     @Override
+    public String getExtension( String extension )
+    {
+        return null;
+    }
+
+    @Override
     public Map<String, String> getAttributes( )
     {
-        if (this.attributes==null) {
+        if ( this.attributes == null )
+        {
             return Collections.emptyMap( );
-        } else {
+        }
+        else
+        {
             return Collections.unmodifiableMap( this.attributes );
         }
     }
@@ -189,6 +211,12 @@
     @Override
     public boolean hasAttributes( )
     {
-        return attributes!=null && attributes.size()>0;
+        return attributes != null && attributes.size( ) > 0;
+    }
+
+    @Override
+    public boolean hasExtension( )
+    {
+        return StringUtils.isNotEmpty( extension );
     }
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaNamespace.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaNamespace.java
new file mode 100644
index 0000000..5371e03
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaNamespace.java
@@ -0,0 +1,163 @@
+/*
+ * 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.archiva.repository.content.base;
+
+import org.apache.archiva.repository.ManagedRepositoryContent;
+import org.apache.archiva.repository.content.Namespace;
+import org.apache.archiva.repository.content.base.builder.NamespaceOptBuilder;
+import org.apache.archiva.repository.content.base.builder.WithAssetBuilder;
+import org.apache.archiva.repository.content.base.builder.WithNamespaceBuilder;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Namespace representation.
+ * Two namespace instances are equal, if the namespace string and the base attributes, like repository and
+ * asset are equal. The separator expression does not influence equality.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ * @since 3.0
+ */
+public class ArchivaNamespace extends ArchivaContentItem implements Namespace
+{
+    private String namespace;
+    private List<String> namespacePath;
+    private String separatorExpression = "\\.";
+
+    private ArchivaNamespace( )
+    {
+
+    }
+
+    @Override
+    public String getNamespace( )
+    {
+        return namespace;
+    }
+
+    @Override
+    public List<String> getNamespacePath( )
+    {
+        return this.namespacePath;
+    }
+
+    public static WithAssetBuilder<WithNamespaceBuilder> withRepository( ManagedRepositoryContent repository )
+    {
+        return new Builder( ).withRepository( repository );
+    }
+
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+        if ( !super.equals( o ) ) return false;
+
+        ArchivaNamespace that = (ArchivaNamespace) o;
+
+        return namespace.equals( that.namespace );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = super.hashCode( );
+        result = 31 * result + namespace.hashCode( );
+        return result;
+    }
+
+    private static class Builder extends ContentItemBuilder<ArchivaNamespace, NamespaceOptBuilder, WithNamespaceBuilder>
+        implements WithNamespaceBuilder, NamespaceOptBuilder
+    {
+
+        private Builder( )
+        {
+            super( new ArchivaNamespace( ) );
+        }
+
+        @Override
+        protected NamespaceOptBuilder getOptBuilder( )
+        {
+            return this;
+        }
+
+        @Override
+        protected WithNamespaceBuilder getNextBuilder( )
+        {
+            return this;
+        }
+
+        @Override
+        public NamespaceOptBuilder withNamespace( String namespace )
+        {
+            if ( namespace == null )
+            {
+                throw new IllegalArgumentException( "Namespace may not be null" );
+            }
+            this.item.namespace = namespace;
+            setNamespacePath( namespace );
+            return this;
+        }
+
+        private void setNamespacePath( String namespace )
+        {
+            if ( StringUtils.isEmpty( namespace ) )
+            {
+                this.item.namespacePath = Collections.emptyList( );
+            }
+            else
+            {
+                this.item.namespacePath = Arrays.asList( namespace.split( this.item.separatorExpression ) );
+            }
+        }
+
+        @Override
+        public NamespaceOptBuilder withSeparatorExpression( String expression )
+        {
+            if ( StringUtils.isEmpty( expression ) )
+            {
+                throw new IllegalArgumentException( "Separator expression may not be null or empty" );
+            }
+            this.item.separatorExpression = expression;
+            try
+            {
+                setNamespacePath( this.item.namespace );
+            }
+            catch ( PatternSyntaxException e )
+            {
+                throw new IllegalArgumentException( "Bad pattern syntax separator expression " + expression + ": " + e.getMessage( ), e );
+            }
+            return this;
+        }
+
+        @Override
+        public ArchivaNamespace build( )
+        {
+            super.build( );
+            return this.item;
+        }
+    }
+
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaProject.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaProject.java
index e520719..4d902f3 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaProject.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaProject.java
@@ -19,41 +19,51 @@
  * under the License.
  */
 
-import org.apache.archiva.repository.ManagedRepository;
-import org.apache.archiva.repository.ManagedRepositoryContent;
-import org.apache.archiva.repository.RepositoryContent;
+import org.apache.archiva.repository.content.Namespace;
 import org.apache.archiva.repository.content.Project;
+import org.apache.archiva.repository.content.base.builder.ProjectOptBuilder;
+import org.apache.archiva.repository.content.base.builder.ProjectWithIdBuilder;
+import org.apache.archiva.repository.content.base.builder.WithNamespaceObjectBuilder;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
 
 /**
  * Immutable class, that represents a project.
+ * <p>
+ * The namespace and id are required attributes for each instance.
+ * <p>
+ * Two project instances are equal if the id, and the namespace are equal and if the base attributes
+ * repository and asset match.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ * @since 3.0
  */
 public class ArchivaProject extends ArchivaContentItem implements Project
 {
-    private String namespace;
+    private Namespace namespace;
     private String id;
-    private RepositoryContent repositoryContent;
-    private StorageAsset asset;
 
     // Setting all setters to private. Builder is the way to go.
-    private ArchivaProject() {
+    private ArchivaProject( )
+    {
 
     }
 
 
     /**
-     * Creates the builder that allows to create a new instance.
-     * @param id the project id, must not be <code>null</code>
+     * Creates the builder for creating new archiva project instances.
+     * You have to set all required attributes before you can call the build() method.
+     *
+     * @param storageAsset the asset
      * @return a builder instance
      */
-    public static Builder withId( String id) {
-        return new Builder( ).withId( id );
+    public static WithNamespaceObjectBuilder withAsset( StorageAsset storageAsset )
+    {
+        return new Builder( ).withAsset( storageAsset );
     }
 
-
     @Override
-    public String getNamespace( )
+    public Namespace getNamespace( )
     {
         return this.namespace;
     }
@@ -64,93 +74,89 @@
         return this.id;
     }
 
+
     @Override
-    public RepositoryContent getRepository( )
+    public boolean equals( Object o )
     {
-        return this.repositoryContent;
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+        if ( !super.equals( o ) ) return false;
+
+        ArchivaProject that = (ArchivaProject) o;
+
+        if ( !namespace.equals( that.namespace ) ) return false;
+        return id.equals( that.id );
     }
 
     @Override
-    public StorageAsset getAsset( )
+    public int hashCode( )
     {
-        return asset;
+        int result = super.hashCode( );
+        result = 31 * result + namespace.hashCode( );
+        result = 31 * result + id.hashCode( );
+        return result;
     }
 
 
-
     /*
-     * Builder interface chaining is used to restrict mandatory attributes
-     * This interface is for the optional arguments.
+     * Builder class
      */
-    public interface OptBuilder {
-
-        OptBuilder withAsset( StorageAsset asset );
-
-        OptBuilder withNamespace( String namespace);
-
-        OptBuilder withAttribute( String key, String value );
-
-    }
-    /*
-     * Builder classes for instantiation
-     */
-    public static final class Builder implements OptBuilder
+    public static final class Builder
+        extends ContentItemBuilder<ArchivaProject, ProjectOptBuilder, WithNamespaceObjectBuilder>
+        implements ProjectOptBuilder, ProjectWithIdBuilder, WithNamespaceObjectBuilder
     {
-        final private ArchivaProject project = new ArchivaProject();
-
         private Builder( )
         {
-
-        }
-
-        private Builder withId(String id) {
-            if ( StringUtils.isEmpty( id ) ) {
-                throw new IllegalArgumentException( "Null or empty value not allowed for id" );
-            }
-            project.id = id;
-            return this;
+            super( new ArchivaProject( ) );
         }
 
 
-        public OptBuilder withRepository( RepositoryContent repository ) {
-            project.repositoryContent = repository;
+        @Override
+        protected ProjectOptBuilder getOptBuilder( )
+        {
             return this;
         }
 
         @Override
-        public OptBuilder withAsset( StorageAsset asset )
+        protected WithNamespaceObjectBuilder getNextBuilder( )
         {
-            project.asset = asset;
             return this;
         }
 
-        public OptBuilder withNamespace( String namespace) {
-            if (namespace==null) {
+        @Override
+        public ProjectOptBuilder withId( String id )
+        {
+            if ( StringUtils.isEmpty( id ) )
+            {
+                throw new IllegalArgumentException( "Null or empty value not allowed for id" );
+            }
+            item.id = id;
+            return this;
+        }
+
+        @Override
+        public ProjectWithIdBuilder withNamespace( Namespace namespace )
+        {
+            if ( namespace == null )
+            {
                 throw new IllegalArgumentException( "Null value not allowed for namespace" );
             }
-            project.namespace = namespace;
+            item.namespace = namespace;
+            super.setRepository( namespace.getRepository( ) );
             return this;
         }
 
-        public OptBuilder withAttribute( String key, String value) {
-            project.putAttribute( key, value );
-            return this;
-        }
-
-        ArchivaProject build() {
-            if (project.namespace==null) {
-                project.namespace = "";
+        @Override
+        public ArchivaProject build( )
+        {
+            super.build( );
+            if ( item.namespace == null )
+            {
+                throw new IllegalArgumentException( "Namespace may not be null" );
             }
-            if (project.asset == null) {
-                if (project.getRepository() instanceof ManagedRepositoryContent) {
-                    project.asset = (( ManagedRepositoryContent)project.getRepository( )).getRepository( ).getAsset( "" );
-                }
-
-            }
-            return project;
+            return item;
         }
     }
 
 
-
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaVersion.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaVersion.java
index b8fd55d..810b8f1 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaVersion.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaVersion.java
@@ -21,22 +21,55 @@
 
 import org.apache.archiva.repository.content.Project;
 import org.apache.archiva.repository.content.Version;
+import org.apache.archiva.repository.content.base.builder.VersionOptBuilder;
+import org.apache.archiva.repository.content.base.builder.WithProjectBuilder;
+import org.apache.archiva.repository.content.base.builder.WithVersionBuilder;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Immutable version instance.
+ * <p>
+ * You have to use the builder method to create instances of this version object.
+ * <p>
+ * The project and the version string are required attributes of this instance additional to the base
+ * attributes repository and asset.
+ * <p>
+ * Two instances are equal, if the project and the version match in addition to the base attributes repository and asset.
+ */
 public class ArchivaVersion extends ArchivaContentItem implements Version
 {
 
     private String version;
-    private StorageAsset asset;
     private Project project;
+    private String separatorExpression = "\\.";
+    private List<String> versionSegments;
 
-    private ArchivaVersion() {
+    private ArchivaVersion( )
+    {
 
     }
 
-    public static ProjectBuilder withVersion(String version) {
-        return new Builder( ).withVersion( version );
+    /**
+     * Creates a new builder for creating new version instances. You have to provide the required
+     * attributes before the build() method can be called.
+     *
+     * @param storageAsset the storage asset
+     * @return the builder for creating new version instances
+     */
+    public static WithProjectBuilder withAsset( StorageAsset storageAsset )
+    {
+        return new Builder( ).withAsset( storageAsset );
+    }
+
+    @Override
+    public List<String> getVersionSegments( )
+    {
+        return versionSegments;
     }
 
     @Override
@@ -46,58 +79,108 @@
     }
 
     @Override
-    public StorageAsset getAsset( )
-    {
-        return asset;
-    }
-
-    @Override
     public Project getProject( )
     {
         return project;
     }
 
-    public interface ProjectBuilder {
-        Builder withProject( Project project );
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+        if ( !super.equals( o ) ) return false;
+
+        ArchivaVersion that = (ArchivaVersion) o;
+
+        if ( !version.equals( that.version ) ) return false;
+        return project.equals( that.project );
     }
 
-    public static final class Builder implements ProjectBuilder {
+    @Override
+    public int hashCode( )
+    {
+        int result = super.hashCode( );
+        result = 31 * result + version.hashCode( );
+        result = 31 * result + project.hashCode( );
+        return result;
+    }
 
-        private ArchivaVersion version  = new ArchivaVersion();
+    private static final class Builder extends ContentItemBuilder<ArchivaVersion, VersionOptBuilder, WithProjectBuilder>
+        implements WithProjectBuilder, WithVersionBuilder, VersionOptBuilder
+    {
 
-        ProjectBuilder withVersion( String version )
+        Builder( )
         {
-            if ( StringUtils.isEmpty( version ) ) {
-                throw new IllegalArgumentException( "Version parameter must not be empty or null." );
-            }
-            this.version.version = version;
-            return this;
+            super( new ArchivaVersion( ) );
         }
 
-
         @Override
-        public Builder withProject( Project project )
+        protected WithProjectBuilder getNextBuilder( )
         {
-            this.version.project = project;
             return this;
         }
 
-        public Builder withAsset( StorageAsset asset )
+        @Override
+        protected VersionOptBuilder getOptBuilder( )
         {
-            this.version.asset = asset;
             return this;
         }
 
-        public Builder withAttribute(String key, String value) {
-            this.version.putAttribute( key, value );
-            return this;
-        }
-
-        public ArchivaVersion build() {
-            if (this.version.asset == null) {
-                this.version.project.getAsset( );
+        @Override
+        public VersionOptBuilder withVersion( String version )
+        {
+            if ( StringUtils.isEmpty( version ) )
+            {
+                throw new IllegalArgumentException( "Version parameter must not be empty or null." );
             }
-            return this.version;
+            item.version = version;
+            updateVersionSegments( );
+            return this;
+        }
+
+
+        private void updateVersionSegments( )
+        {
+            item.versionSegments = Arrays.asList( item.version.split( item.separatorExpression ) );
+        }
+
+        @Override
+        public WithVersionBuilder withProject( Project project )
+        {
+            if ( project == null )
+            {
+                throw new IllegalArgumentException( "Project may not be null" );
+            }
+            item.project = project;
+            super.setRepository( project.getRepository( ) );
+            return this;
+        }
+
+        public ArchivaVersion build( )
+        {
+            super.build( );
+            return item;
+        }
+
+        @Override
+        public VersionOptBuilder withSeparatorExpression( String expression )
+        {
+            if ( StringUtils.isEmpty( expression ) )
+            {
+                throw new IllegalArgumentException( "Separator expression may not be null or empty" );
+            }
+            this.item.separatorExpression = expression;
+            try
+            {
+                updateVersionSegments( );
+            }
+            catch ( PatternSyntaxException e )
+            {
+                throw new IllegalArgumentException( "Bad separator expression " + expression + ": " + e.getMessage( ), e );
+            }
+            return this;
         }
     }
 
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactClassifierMapping.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactClassifierMapping.java
index fd98807..79a5f78 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactClassifierMapping.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactClassifierMapping.java
@@ -24,7 +24,7 @@
 
 /**
  * Maven 1.x request type to classifier mapping for translating to a Maven 2.x storage
- *
+ * <p>
  * TODO reuse mappings for other repositories
  *
  * @since 1.1
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactUtil.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactUtil.java
index 9874778..6c90323 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactUtil.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArtifactUtil.java
@@ -34,7 +34,8 @@
  * Utility class that gives information about the physical location of artifacts.
  */
 @Service( "ArtifactUtil#default" )
-public class ArtifactUtil {
+public class ArtifactUtil
+{
 
     @Inject
     RepositoryContentFactory repositoryContentFactory;
@@ -43,30 +44,32 @@
      * Returns the physical location of a given artifact in the repository. There is no check for the
      * existence of the returned file.
      *
-     * @param repository The repository, where the artifact is stored.
+     * @param repository        The repository, where the artifact is stored.
      * @param artifactReference The artifact reference.
      * @return The absolute path to the artifact.
      * @throws RepositoryException
      */
-    public Path getArtifactPath(ManagedRepository repository, ArtifactReference artifactReference) throws RepositoryException {
-        final ManagedRepositoryContent content = repositoryContentFactory.getManagedRepositoryContent(repository);
+    public Path getArtifactPath( ManagedRepository repository, ArtifactReference artifactReference ) throws RepositoryException
+    {
+        final ManagedRepositoryContent content = repositoryContentFactory.getManagedRepositoryContent( repository );
         final String artifactPath = content.toPath( artifactReference );
-        return Paths.get(repository.getLocation()).resolve(artifactPath);
+        return Paths.get( repository.getLocation( ) ).resolve( artifactPath );
     }
 
     /**
      * Returns the physical location of a given artifact in the repository. There is no check for the
      * existence of the returned file.
      *
-     * @param repository The repository, where the artifact is stored.
+     * @param repository        The repository, where the artifact is stored.
      * @param artifactReference The artifact reference.
      * @return The asset representation of the artifact.
      * @throws RepositoryException
      */
-    public StorageAsset getArtifactAsset(ManagedRepository repository, ArtifactReference artifactReference) throws RepositoryException {
-        final ManagedRepositoryContent content = repositoryContentFactory.getManagedRepositoryContent(repository);
+    public StorageAsset getArtifactAsset( ManagedRepository repository, ArtifactReference artifactReference ) throws RepositoryException
+    {
+        final ManagedRepositoryContent content = repositoryContentFactory.getManagedRepositoryContent( repository );
         final String artifactPath = content.toPath( artifactReference );
-        return repository.getAsset(artifactPath);
+        return repository.getAsset( artifactPath );
     }
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactOptBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactOptBuilder.java
new file mode 100644
index 0000000..3ac93c1
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactOptBuilder.java
@@ -0,0 +1,41 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.base.ArchivaArtifact;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface ArtifactOptBuilder
+    extends OptBuilder<ArchivaArtifact, ArtifactOptBuilder>
+{
+
+    ArtifactOptBuilder withArtifactVersion( String version );
+
+    ArtifactOptBuilder withType( String type );
+
+    ArtifactOptBuilder withClassifier( String classifier );
+
+    ArtifactOptBuilder withRemainder( String remainder );
+
+    ArtifactOptBuilder withContentType( String contentType );
+
+    ArchivaArtifact build( );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactVersionBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactVersionBuilder.java
new file mode 100644
index 0000000..0ca533d
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactVersionBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface ArtifactVersionBuilder
+{
+    ArtifactOptBuilder withArtifactVersion( String version );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactWithIdBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactWithIdBuilder.java
new file mode 100644
index 0000000..3245ae3
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ArtifactWithIdBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface ArtifactWithIdBuilder
+{
+    ArtifactOptBuilder withId( String id );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/NamespaceOptBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/NamespaceOptBuilder.java
new file mode 100644
index 0000000..3862939
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/NamespaceOptBuilder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.base.ArchivaNamespace;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface NamespaceOptBuilder
+    extends OptBuilder<ArchivaNamespace, NamespaceOptBuilder>
+{
+
+    NamespaceOptBuilder withSeparatorExpression( String expression );
+
+    ArchivaNamespace build( );
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/OptBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/OptBuilder.java
new file mode 100644
index 0000000..c1d1dc1
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/OptBuilder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.ContentItem;
+
+/**
+ * Optional attributes. Subclasses should inherit from this interface for their own optional
+ * interface
+ *
+ * @param <O> the target builder interface for the optional attributes
+ */
+public interface OptBuilder<I extends ContentItem, O extends OptBuilder>
+{
+    O withAttribute( String key, String value );
+
+    I build( );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectOptBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectOptBuilder.java
new file mode 100644
index 0000000..4964e13
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectOptBuilder.java
@@ -0,0 +1,37 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.base.ArchivaProject;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */ /*
+ * Builder interface chaining is used to restrict mandatory attributes
+ * This interface is for the optional arguments.
+ */
+public interface ProjectOptBuilder
+    extends OptBuilder<ArchivaProject, ProjectOptBuilder>
+{
+
+    ProjectOptBuilder withId( String id );
+
+    ArchivaProject build( );
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectWithIdBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectWithIdBuilder.java
new file mode 100644
index 0000000..e315c31
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/ProjectWithIdBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface ProjectWithIdBuilder
+{
+    ProjectOptBuilder withId( String id );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/VersionOptBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/VersionOptBuilder.java
new file mode 100644
index 0000000..33e133e
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/VersionOptBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.base.ArchivaVersion;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface VersionOptBuilder
+    extends OptBuilder<ArchivaVersion, VersionOptBuilder>
+{
+
+    VersionOptBuilder withSeparatorExpression( String expression );
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithAssetBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithAssetBuilder.java
new file mode 100644
index 0000000..321e411
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithAssetBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithAssetBuilder<N>
+{
+    N withAsset( StorageAsset asset );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceBuilder.java
new file mode 100644
index 0000000..4ffe071
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithNamespaceBuilder
+{
+    NamespaceOptBuilder withNamespace( String expression );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceObjectBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceObjectBuilder.java
new file mode 100644
index 0000000..da86138
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithNamespaceObjectBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.Namespace;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithNamespaceObjectBuilder
+{
+    ProjectWithIdBuilder withNamespace( Namespace namespace );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithProjectBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithProjectBuilder.java
new file mode 100644
index 0000000..28e4663
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithProjectBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.Project;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithProjectBuilder
+{
+    WithVersionBuilder withProject( Project project );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithRepositoryBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithRepositoryBuilder.java
new file mode 100644
index 0000000..d4fdb09
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithRepositoryBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.ManagedRepositoryContent;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithRepositoryBuilder<N>
+{
+    WithAssetBuilder<N> withRepository( ManagedRepositoryContent repository );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionBuilder.java
new file mode 100644
index 0000000..3a13fce
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithVersionBuilder
+{
+    VersionOptBuilder withVersion( String version );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionObjectBuilder.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionObjectBuilder.java
new file mode 100644
index 0000000..bdb387e
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/builder/WithVersionObjectBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.archiva.repository.content.base.builder;
+
+import org.apache.archiva.repository.content.Version;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface WithVersionObjectBuilder
+{
+    ArtifactWithIdBuilder withVersion( Version version );
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaArtifactTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaArtifactTest.java
new file mode 100644
index 0000000..fb30796
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaArtifactTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.archiva.repository.content.base;
+
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class ArchivaArtifactTest extends ContentItemTest
+{
+
+    ArchivaProject project;
+    ArchivaVersion version;
+
+    @BeforeEach
+    void init() {
+        ArchivaNamespace namespace = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.def" ).build();
+        project = ArchivaProject.withAsset( asset ).withNamespace( namespace ).withId( "proj001" ).build( );
+        version = ArchivaVersion.withAsset( asset ).withProject( project ).withVersion( "2.33.1" ).build();
+    }
+
+
+    @Override
+    public OptBuilder getBuilder( )
+    {
+        return ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" );
+    }
+
+
+    @Test
+    void idOnly() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" ).build( );
+
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "", item.getArtifactVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "", item.getContentType( ) );
+        assertNotNull( item.getId( ) );
+        assertEquals( "testartifact", item.getId( ) );
+
+    }
+
+
+    @Test
+    void withArtifactVersion() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "1.0.10494949" )
+            .build( );
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "1.0.10494949", item.getArtifactVersion( ) );
+    }
+
+    @Test
+    void withType() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withType( "javadoc" )
+            .build( );
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "javadoc", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "", item.getArtifactVersion( ) );
+    }
+
+    @Test
+    void withClassifier() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withClassifier( "source" )
+            .build( );
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "source", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "", item.getArtifactVersion( ) );
+    }
+
+    @Test
+    void withRemainder() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withRemainder( "jar.md5" )
+            .build( );
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "jar.md5", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "", item.getArtifactVersion( ) );
+    }
+
+    @Test
+    void withContentType() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withContentType( "text/xml" )
+            .build( );
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "text/xml", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "", item.getArtifactVersion( ) );
+    }
+
+    @Test
+    void withAllAttributes() {
+        ArchivaArtifact item = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals(repository, item.getRepository());
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( version, item.getVersion( ) );
+        assertNotNull( item.getType( ) );
+        assertEquals( "jar", item.getType( ) );
+        assertNotNull( item.getClassifier( ) );
+        assertEquals( "private", item.getClassifier( ) );
+        assertNotNull( item.getRemainder( ) );
+        assertEquals( "jar.sha1", item.getRemainder( ) );
+        assertNotNull( item.getContentType( ) );
+        assertEquals( "text/html", item.getContentType( ) );
+        assertEquals( "testartifact", item.getId( ) );
+        assertNotNull( item.getArtifactVersion( ) );
+        assertEquals( "3.0.484848", item.getArtifactVersion( ) );
+    }
+
+
+    @Test
+    void equality() {
+        ArchivaArtifact item1 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item2 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484849" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item3 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item4 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "sourcejar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item5 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "public" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item6 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.md5" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item7 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/xml" )
+            .build( );
+
+        ArchivaArtifact item8 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact2" )
+            .withArtifactVersion( "3.0.484848" )
+            .withType( "jar" )
+            .withClassifier( "private" )
+            .withRemainder( "jar.sha1" )
+            .withContentType( "text/html" )
+            .build( );
+
+        ArchivaArtifact item9 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact2" )
+            .build( );
+
+        ArchivaArtifact item10 = ArchivaArtifact.withAsset( asset ).withVersion( version ).withId( "testartifact2" )
+            .build( );
+
+
+        assertNotEquals( item1, item2 );
+        assertEquals( item1, item3 );
+        assertNotEquals( item1, item4 );
+        assertNotEquals( item1, item5 );
+        // remainder and content type are ignored
+        assertEquals( item1, item6 );
+        assertEquals( item1, item7 );
+
+        assertNotEquals( item1, item8 );
+        assertEquals( item9, item10 );
+
+    }
+
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaNamespaceTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaNamespaceTest.java
new file mode 100644
index 0000000..93774bd
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaNamespaceTest.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.archiva.repository.content.base;
+
+import org.apache.archiva.repository.content.base.builder.NamespaceOptBuilder;
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.apache.archiva.repository.content.base.builder.WithNamespaceBuilder;
+import org.apache.archiva.repository.mock.ManagedRepositoryContentMock;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ *
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class ArchivaNamespaceTest extends ContentItemTest
+{
+
+
+    @Test
+    void nullNamespaceThrowsIllegalArgumentException() {
+        WithNamespaceBuilder builder = ArchivaNamespace.withRepository( repository ).withAsset( asset );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withNamespace( null ) );
+    }
+
+    @Test
+    public void namespaceOnly() throws IOException
+    {
+        assertNotNull( this.repository );
+        ArchivaNamespace result = ArchivaNamespace.withRepository( repository )
+            .withAsset( this.asset ).withNamespace( "test.1d.d" ).build( );
+
+        assertNotNull( result );
+        assertEquals( this.repository, result.getRepository( ) );
+        assertEquals( this.asset, result.getAsset( ) );
+        assertEquals( "test.1d.d", result.getNamespace( ) );
+        assertNotNull( result.getAttributes( ) );
+        assertEquals( 0, result.getAttributes( ).size( ) );
+        assertArrayEquals( new String[]{ "test", "1d", "d" }, result.getNamespacePath().toArray() );
+    }
+
+    @Test
+    public void namespaceWithEmptyPart() throws IOException
+    {
+        assertNotNull( this.repository );
+        ArchivaNamespace result = ArchivaNamespace.withRepository( repository )
+            .withAsset( this.asset ).withNamespace( "test.1d..d." ).build( );
+
+        assertNotNull( result );
+        assertEquals( this.repository, result.getRepository( ) );
+        assertEquals( this.asset, result.getAsset( ) );
+        assertEquals( "test.1d..d.", result.getNamespace( ) );
+        assertNotNull( result.getAttributes( ) );
+        assertEquals( 0, result.getAttributes( ).size( ) );
+        assertArrayEquals( new String[]{ "test", "1d", "","d" }, result.getNamespacePath().toArray() );
+    }
+
+    @Test
+    public void withSeparatorExpression() throws IOException
+    {
+        assertNotNull( this.repository );
+        ArchivaNamespace result = ArchivaNamespace.withRepository( repository )
+            .withAsset( this.asset ).withNamespace( "test.1d.d/abc/def" )
+            .withSeparatorExpression( "/" )
+            .build( );
+
+        assertNotNull( result );
+        assertEquals( this.repository, result.getRepository( ) );
+        assertEquals( this.asset, result.getAsset( ) );
+        assertEquals( "test.1d.d/abc/def", result.getNamespace( ) );
+        assertNotNull( result.getAttributes( ) );
+        assertEquals( 0, result.getAttributes( ).size( ) );
+        assertArrayEquals( new String[]{ "test.1d.d", "abc", "def" }, result.getNamespacePath().toArray() );
+    }
+
+    @Test
+    void badSeparatorExpression() {
+        NamespaceOptBuilder builder = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.de/abc" );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withSeparatorExpression( null ) );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withSeparatorExpression( "(" ) );
+    }
+
+
+    @Test
+    void equalityTests() {
+        ArchivaNamespace ns1 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.de/abc" ).build();
+        ArchivaNamespace ns2 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.de/abc" ).build();
+        ArchivaNamespace ns3 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.de/abcd" ).build();
+        ArchivaNamespace ns4 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.de/abc" ).
+            withSeparatorExpression( "/" ).build();
+        ArchivaNamespace ns5 = ArchivaNamespace.withRepository( new ManagedRepositoryContentMock() ).withAsset( asset ).withNamespace( "abc.de/abc" ).build();
+
+        assertFalse( ns1 == ns2 );
+        assertEquals( ns1, ns2 );
+        assertNotEquals( ns1, ns3 );
+        assertEquals( ns1, ns4 );
+        assertNotEquals( ns1, ns5 );
+
+    }
+
+
+    @Override
+    public OptBuilder getBuilder( )
+    {
+        return ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.def" );
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaProjectTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaProjectTest.java
new file mode 100644
index 0000000..e14ec5c
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaProjectTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.archiva.repository.content.base;
+
+import org.apache.archiva.repository.content.Namespace;
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.apache.archiva.repository.content.base.builder.ProjectWithIdBuilder;
+import org.apache.archiva.repository.content.base.builder.WithNamespaceObjectBuilder;
+import org.apache.archiva.repository.mock.ManagedRepositoryContentMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class ArchivaProjectTest extends ContentItemTest
+{
+
+    Namespace namespace;
+
+    @BeforeEach
+    void init() {
+        namespace = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "test.namespace.123" ).build();
+    }
+
+    @Override
+    public OptBuilder getBuilder( )
+    {
+        return ArchivaProject.withAsset( asset ).withNamespace( namespace ).withId( "abcde" );
+    }
+
+    @Test
+    void withNamespaceAndId() {
+        ArchivaProject item = ArchivaProject.withAsset( asset ).withNamespace( namespace )
+            .withId( "abcdefg" ).build( );
+
+        assertNotNull( item );
+        assertEquals( "abcdefg", item.getId( ) );
+        assertNotNull( item.getNamespace( ) );
+        assertEquals( namespace, item.getNamespace( ) );
+        assertNotNull( item.getRepository( ) );
+        assertEquals( repository, item.getRepository( ) );
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+
+    }
+
+    @Test
+    void illegalNamespace() {
+        WithNamespaceObjectBuilder builder = ArchivaProject.withAsset( asset );
+
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withNamespace( null ) );
+    }
+
+    @Test
+    void illegalId() {
+        ProjectWithIdBuilder builder = ArchivaProject.withAsset( asset ).withNamespace( namespace );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withId( null ) );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withId( "" ) );
+    }
+
+    @Test
+    void equalityTests() {
+        ArchivaProject item1 = ArchivaProject.withAsset( asset ).withNamespace( namespace )
+            .withId( "abcdefgtest1" ).build( );
+        ArchivaProject item2 = ArchivaProject.withAsset( asset ).withNamespace( namespace )
+            .withId( "abcdefgtest2" ).build( );
+        ArchivaProject item3 = ArchivaProject.withAsset( asset ).withNamespace( namespace )
+            .withId( "abcdefgtest1" ).build( );
+        ArchivaNamespace ns2 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "test.namespace.123" ).build( );
+        ArchivaProject item4 = ArchivaProject.withAsset( asset ).withNamespace( ns2 )
+            .withId( "abcdefgtest1" ).build( );
+        ArchivaNamespace ns3 = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "test.namespace.1234" ).build( );
+        ArchivaProject item5 = ArchivaProject.withAsset( asset ).withNamespace( ns3 )
+            .withId( "abcdefgtest1" ).build( );
+
+        assertNotEquals( item1, item2 );
+        assertEquals( item1, item3 );
+        assertEquals( item1, item4 );
+        assertNotEquals( item1, item5 );
+
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaVersionTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaVersionTest.java
new file mode 100644
index 0000000..151bb23
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ArchivaVersionTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.archiva.repository.content.base;
+
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.apache.archiva.repository.content.base.builder.WithVersionBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class ArchivaVersionTest extends ContentItemTest
+{
+
+    ArchivaProject project;
+
+    @BeforeEach
+    void init() {
+        ArchivaNamespace namespace = ArchivaNamespace.withRepository( repository ).withAsset( asset ).withNamespace( "abc.def" ).build();
+        project = ArchivaProject.withAsset( asset ).withNamespace( namespace ).withId( "proj001" ).build( );
+    }
+
+    @Override
+    public OptBuilder getBuilder( )
+    {
+        return ArchivaVersion.withAsset( asset ).withProject( project ).withVersion( "1.5.8" );
+    }
+
+    @Test
+    void versionOnly() {
+        ArchivaVersion item = ArchivaVersion.withAsset( asset ).withProject( project ).withVersion( "3.4.5" ).build();
+        assertNotNull( item );
+        assertNotNull( item.getRepository( ) );
+        assertEquals( repository, item.getRepository( ) );
+        assertNotNull( item.getAsset( ) );
+        assertEquals( asset, item.getAsset( ) );
+        assertNotNull( item.getProject( ) );
+        assertEquals( project, item.getProject( ) );
+        assertNotNull( item.getVersion( ) );
+        assertEquals( "3.4.5", item.getVersion( ) );
+        assertNotNull( item.getVersionSegments( ) );
+        assertArrayEquals( new String[]{"3", "4", "5"}, item.getVersionSegments( ).toArray( ) );
+    }
+
+    @Test
+    void versionSegments() {
+        ArchivaVersion item = ArchivaVersion.withAsset( asset ).withProject( project ).withVersion( "3.455.5" ).build();
+        assertNotNull( item );
+        assertNotNull( item.getVersionSegments( ) );
+        assertArrayEquals( new String[]{"3", "455", "5"}, item.getVersionSegments( ).toArray( ) );
+
+        ArchivaVersion item2 = ArchivaVersion.withAsset( asset ).withProject( project ).withVersion( "xd43.455.5" ).build();
+        assertNotNull( item2 );
+        assertNotNull( item2.getVersionSegments( ) );
+        assertArrayEquals( new String[]{"xd43", "455", "5"}, item2.getVersionSegments( ).toArray( ) );
+    }
+
+    @Test
+    void illegalVersion() {
+        WithVersionBuilder builder = ArchivaVersion.withAsset( asset ).withProject( project );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withVersion( null ) );
+        assertThrows( IllegalArgumentException.class, ( ) -> builder.withVersion( "" ) );
+    }
+
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ContentItemTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ContentItemTest.java
new file mode 100644
index 0000000..a1c3fd6
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/content/base/ContentItemTest.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.archiva.repository.content.base;
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.repository.content.ContentItem;
+import org.apache.archiva.repository.content.base.builder.OptBuilder;
+import org.apache.archiva.repository.mock.ManagedRepositoryContentMock;
+import org.apache.archiva.repository.storage.FilesystemStorage;
+import org.apache.archiva.repository.storage.StorageAsset;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public abstract class ContentItemTest
+{
+    protected ManagedRepositoryContentMock repository;
+    protected FilesystemStorage storage;
+    protected StorageAsset asset;
+
+    @BeforeEach
+    public void setup() throws IOException
+    {
+        this.repository = new ManagedRepositoryContentMock( );
+
+        this.storage = new FilesystemStorage( Paths.get( "target" ), new DefaultFileLockManager( ) );
+
+        this.asset = storage.getAsset( "" );
+
+    }
+
+    public abstract OptBuilder getBuilder();
+
+    @Test
+    void testWithAttribute() {
+        ContentItem test = getBuilder( ).withAttribute( "testkey1", "testvalue1" ).withAttribute( "testkey2", "testvalue2" ).build( );
+
+        assertNotNull( test.getAttributes( ) );
+        assertEquals( 2, test.getAttributes( ).size( ) );
+        assertEquals( "testvalue1", test.getAttribute( "testkey1" ) );
+        assertEquals( "testvalue2", test.getAttribute( "testkey2" ) );
+        assertNull( test.getAttribute( "key123" ) );
+
+    }
+
+}