]> source.dussan.org Git - archiva.git/commitdiff
Improving storage handling with assets
authorMartin Stockhammer <martin_s@apache.org>
Sun, 30 May 2021 17:14:03 +0000 (19:14 +0200)
committerMartin Stockhammer <martin_s@apache.org>
Sun, 30 May 2021 17:14:03 +0000 (19:14 +0200)
23 files changed:
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
archiva-modules/archiva-base/archiva-repository-scanner/src/test/java/org/apache/archiva/repository/scanner/mock/ManagedRepositoryContentMock.java
archiva-modules/archiva-base/archiva-storage-api/pom.xml
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetType.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/StorageAsset.java
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/StorageUtil.java
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockStorage.java
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AbstractStorageUtilTest.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StorageUtilTest.java
archiva-modules/archiva-base/archiva-storage-fs/pom.xml
archiva-modules/archiva-base/archiva-storage-fs/src/main/java/org/apache/archiva/repository/storage/fs/FilesystemAsset.java
archiva-modules/archiva-base/archiva-storage-fs/src/test/java/org/apache/archiva/repository/storage/fs/FileSystemStorageUtilTest.java [new file with mode: 0644]
archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/content/ManagedDefaultRepositoryContent.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/FileInfo.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/MavenManagedRepository.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/MavenManagedRepositoryUpdate.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/MavenManagedRepositoryService.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultMavenManagedRepositoryService.java
archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/DavResourceTest.java

index 46e32e7f7ecb020b9bdfc0eb270440d1541bf387..61778d2c8873432ab831d54835bcf04c1af90433 100644 (file)
@@ -90,6 +90,30 @@ public interface ManagedRepositoryContent extends RepositoryContent
      */
     void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException;
 
+    /**
+     * Copies the given item to the destination repository. The destination repository must be of the same type
+     * as this repository. Metadata is updated only if there is no metadata scan consumer is active.
+     *
+     * @param item the item to copy
+     * @param destinationRepository the destination repository
+     * @throws ItemNotFoundException if the given item does not exist
+     * @throws ContentAccessException if an error occurred during the copy process
+     */
+    void copyItem(ContentItem item, ManagedRepository destinationRepository) throws ItemNotFoundException, ContentAccessException;
+
+    /**
+     * Copies the given item to the destination repository. The destination repository must be of the same type
+     * as this repository.
+     *
+     * @param item the item to copy
+     * @param destinationRepository the destination repository
+     * @param updateMetadata <code>true</code>, if the metadata will be updated immediately after copying. <code>false</code>
+     *                       if metadata is not updated after copying, but it may be updated by the metadata scan consumer, if it is configured.
+     * @throws ItemNotFoundException if the given item does not exist
+     * @throws ContentAccessException if an error occurred during the copy process
+     */
+    void copyItem(ContentItem item, ManagedRepository destinationRepository, boolean updateMetadata) throws ItemNotFoundException, ContentAccessException;
+
     /**
      * Returns a item for the given selector. The type of the returned item depends on the
      * selector.
index a989942d4775cbd257bc7be409c5c2f99de003fb..c0aefcbddd538e37f47d4ebe2f574a435e2e5bae 100644 (file)
@@ -114,6 +114,18 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
 
     }
 
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository, boolean updateMetadata ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
     @Override
     public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
     {
index aeee40b5d045e0b2c6086eb473ef0f544e0e3f6f..9280bd747a5e3dc43f60f7491389288ca23f5217 100644 (file)
@@ -125,7 +125,19 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
     }
 
     @Override
-    public void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException
+    public void deleteItem( ContentItem item ) throws ContentAccessException
+    {
+
+    }
+
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository ) throws ContentAccessException
+    {
+
+    }
+
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository, boolean updateMetadata ) throws ContentAccessException
     {
 
     }
@@ -161,7 +173,7 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
     }
 
     @Override
-    public Artifact getArtifact( String path ) throws LayoutException, ContentAccessException
+    public Artifact getArtifact( String path ) throws ContentAccessException
     {
         return null;
     }
@@ -252,13 +264,13 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
     }
 
     @Override
-    public <T extends ContentItem> T applyCharacteristic( Class<T> clazz, ContentItem item ) throws LayoutException
+    public <T extends ContentItem> T applyCharacteristic( Class<T> clazz, ContentItem item )
     {
         return null;
     }
 
     @Override
-    public <T extends ManagedRepositoryContentLayout> T getLayout( Class<T> clazz ) throws LayoutException
+    public <T extends ManagedRepositoryContentLayout> T getLayout( Class<T> clazz )
     {
         return null;
     }
@@ -296,7 +308,7 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
 
 
     @Override
-    public ContentItem toItem( String path ) throws LayoutException
+    public ContentItem toItem( String path )
     {
         return null;
     }
index 9085887fe64401df22a2a8d6a5b5f998ff5b6430..3bc8be6b073d15615d2a50c8a57c3c1a33b111cb 100644 (file)
       <artifactId>log4j-slf4j-impl</artifactId>
       <scope>test</scope>
     </dependency>
+
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
 
   <build>
     <plugins>
+      <!-- We need the abstract test class for other projects and are creating a jar file with test classifier -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetType.java b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetType.java
new file mode 100644 (file)
index 0000000..e38c8a9
--- /dev/null
@@ -0,0 +1,29 @@
+package org.apache.archiva.repository.storage;
+/*
+ * 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.
+ */
+
+/**
+ * The asset type is only used for creating new assets.
+ * FILE type contains data and provides input and output stream
+ * CONTAINER type contains only further assets and not data
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public enum AssetType
+{
+    FILE,CONTAINER
+}
index eb7074c0ff3b3dd5b4805bea367489ad6d388edc..12f425fed857e62e4952d0ad5f6ea567f335f189 100644 (file)
@@ -43,13 +43,14 @@ public interface StorageAsset
 {
 
     /**
-     * Returns the storage this asset belongs to.
-     * @return
+     * Returns the storage this asset belongs to. Each asset belongs to exactly one storage instance.
+     *
+     * @return the storage instance
      */
     RepositoryStorage getStorage();
 
     /**
-     * Returns the complete path relative to the repository to the given asset.
+     * Returns the complete path relative to the base path to the given asset.
      *
      * @return A path starting with '/' that uniquely identifies the asset in the repository.
      */
@@ -81,7 +82,7 @@ public interface StorageAsset
     boolean isLeaf();
 
     /**
-     * List the child assets.
+     * List the child assets. Implementations should return a ordered list of children.
      *
      * @return The list of children. If there are no children and if the asset is not a container, a empty list will be returned.
      */
@@ -96,40 +97,55 @@ public interface StorageAsset
 
     /**
      * Returns the input stream of the artifact content.
-     * It will throw a IOException, if the stream could not be created.
+     * This method will throw a IOException, if the stream could not be created.
+     * Assets of type {@link AssetType#CONTAINER} will throw an IOException because they have no data attached.
      * Implementations should create a new stream instance for each invocation and make sure that the
      * stream is proper closed after usage.
      *
      * @return The InputStream representing the content of the artifact.
-     * @throws IOException
+     * @throws IOException if the stream could not be created, either because of a problem accessing the storage, or because
+     * the asset is not capable to provide a data stream
      */
     InputStream getReadStream() throws IOException;
 
     /**
      * Returns a NIO representation of the data.
-     *
+     * This method will throw a IOException, if the stream could not be created.
+     * Assets of type {@link AssetType#CONTAINER} will throw an IOException because they have no data attached.
+     * Implementations should create a new channel instance for each invocation and make sure that the
+     * channel is proper closed after usage.
+
      * @return A channel to the asset data.
-     * @throws IOException
+     * @throws IOException if the channel could not be created, either because of a problem accessing the storage, or because
+     * the asset is not capable to provide a data stream
      */
     ReadableByteChannel getReadChannel() throws IOException;
 
     /**
      *
      * Returns an output stream where you can write data to the asset. The operation is not locked or synchronized.
+     * This method will throw a IOException, if the stream could not be created.
+     * Assets of type {@link AssetType#CONTAINER} will throw an IOException because they have no data attached.
      * User of this method have to make sure, that the stream is proper closed after usage.
      *
      * @param replace If true, the original data will be replaced, otherwise the data will be appended.
      * @return The OutputStream where the data can be written.
-     * @throws IOException
+     * @throws IOException if the stream could not be created, either because of a problem accessing the storage, or because
+     * the asset is not capable to provide a data stream
      */
     OutputStream getWriteStream( boolean replace) throws IOException;
 
     /**
      * Returns a NIO representation of the asset where you can write the data.
+     * This method will throw a IOException, if the stream could not be created.
+     * Assets of type {@link AssetType#CONTAINER} will throw an IOException because they have no data attached.
+     * Implementations should create a new channel instance for each invocation and make sure that the
+     * channel is proper closed after usage.
      *
      * @param replace True, if the content should be replaced by the data written to the stream.
      * @return The Channel for writing the data.
-     * @throws IOException
+     * @throws IOException if the channel could not be created, either because of a problem accessing the storage, or because
+     * the asset is not capable to provide a data stream
      */
     WritableByteChannel getWriteChannel( boolean replace) throws IOException;
 
@@ -140,6 +156,7 @@ public interface StorageAsset
      * The original file may be deleted, if the storage was successful.
      *
      * @param newData Replaces the data by the content of the given file.
+     * @throws IOException if the access to the storage failed
      */
     boolean replaceDataFromFile( Path newData) throws IOException;
 
@@ -155,6 +172,13 @@ public interface StorageAsset
      */
     void create() throws IOException;
 
+    /**
+     * Creates the asset as the given type
+     * @param type the type to create, if the asset does not exist
+     * @throws IOException if the asset could not be created
+     */
+    void create(AssetType type) throws IOException;
+
     /**
      * Returns the real path to the asset, if it exist. Not all implementations may implement this method.
      * The method throws {@link UnsupportedOperationException}, if and only if {@link #isFileBased()} returns false.
@@ -166,7 +190,7 @@ public interface StorageAsset
 
     /**
      * Returns true, if the asset can return a file path for the given asset. If this is true, the  {@link #getFilePath()}
-     * will not throw a {@link UnsupportedOperationException}
+     * must not throw a {@link UnsupportedOperationException}
      *
      * @return
      */
@@ -174,20 +198,33 @@ public interface StorageAsset
 
     /**
      * Returns true, if there is a parent to this asset.
-     * @return
+     * @return True, if this asset is a descendant of a parent. False, if this is the root asset of the storage.
      */
     boolean hasParent();
 
     /**
-     * Returns the parent of this asset.
+     * Returns the parent of this asset. If this is the root asset of the underlying storage,
+     * <code>null</code> will be returned.
+     *
      * @return The asset, or <code>null</code>, if it does not exist.
      */
     StorageAsset getParent();
 
     /**
-     * Returns the asset relative to the given path
-     * @param toPath
-     * @return
+     * Returns the asset instance relative to the given path. The returned asset may not persisted yet.
+     *
+     * @param toPath the path relative to the current asset
+     * @return the asset representing the given path
      */
     StorageAsset resolve(String toPath);
+
+    /**
+     * Returns the relative path from <code>this</code> asset to the given asset.
+     * If the given asset is from a different storage implementation, than this asset, the
+     * result is undefined.
+     *
+     * @param asset the asset that should be a descendant of <code>this</code>
+     * @return the relative path
+     */
+    String relativize(StorageAsset asset );
 }
index bd52e4b6d3abec80a67badd99a8f62a144619581..5f64d4d764162e439409e25856361b0ad6fe456a 100644 (file)
@@ -18,6 +18,7 @@ package org.apache.archiva.repository.storage.util;
  * under the License.
  */
 
+import org.apache.archiva.repository.storage.AssetType;
 import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
@@ -31,8 +32,14 @@ import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import java.nio.file.CopyOption;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
@@ -135,42 +142,118 @@ public class StorageUtil
         final RepositoryStorage storage = baseDir.getStorage( );
         try(Stream<StorageAsset> stream = newAssetStream( baseDir ))
         {
-            if ( stopOnError )
+            try
             {
                 // Return true, if no exception occurred
                 // anyMatch is short-circuiting, that means it stops if the condition matches
-                return !stream.map( a -> {
-                    try
-                    {
-                        storage.removeAsset( a );
-                        // Returning false, if OK
-                        return Boolean.FALSE;
-                    }
-                    catch ( IOException e )
-                    {
-                        LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
-                        // Returning true, if exception
-                        return Boolean.TRUE;
-                    }
-                } ).anyMatch( r -> r );
-            } else {
-                // Return true, if all removals were OK
-                // We want to consume all, so we use allMatch
                 return stream.map( a -> {
                     try
                     {
                         storage.removeAsset( a );
-                        // Returning true, if OK
                         return Boolean.TRUE;
                     }
                     catch ( IOException e )
                     {
                         LOG.error( "Could not delete asset {}: {}", a.getPath( ), e.getMessage( ), e );
-                        // Returning false, if exception
-                        return Boolean.FALSE;
+                        if (stopOnError)  {
+                            throw new RuntimeException( e );
+                        } else
+                        {
+                            return Boolean.FALSE;
+                        }
+                    }
+                } ).reduce( (a,b)->Boolean.logicalAnd( a,b ) ).orElse( Boolean.FALSE );
+            } catch ( RuntimeException e ) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Deletes the given asset and all child assets recursively.
+     * @param srcAsset The source directory
+     * @param destAsset The destination directory
+     * @param stopOnError if <code>true</code> the traversal stops, if an exception is encountered
+     * @return returns <code>true</code>, if every item was removed. If an IOException was encountered during
+     * traversal it returns <code>false</code>
+     */
+    public static final boolean copyRecursively(final StorageAsset srcAsset, final StorageAsset destAsset, final boolean stopOnError) throws IOException
+    {
+        try
+        {
+            if ( srcAsset.isFileBased( ) && destAsset.isFileBased( ) )
+            {
+                Path src = srcAsset.getFilePath( );
+                Path dest = destAsset.getFilePath( );
+                return Files.walk( src )
+                    .map( source -> {
+                        try
+                        {
+                            Files.copy( source, dest.resolve( src.relativize( source ) ),
+                                StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES );
+                            return Boolean.TRUE;
+                        }
+                        catch ( IOException e )
+                        {
+                            if ( stopOnError )
+                            {
+                                throw new RuntimeException( e );
+                            }
+                            else
+                            {
+                                return Boolean.FALSE;
+                            }
+                        }
+                    } ).reduce( ( a, b ) -> Boolean.logicalAnd( a, b ) ).get();
+            }
+            else
+            {
+                try ( Stream<StorageAsset> stream = newAssetStream( srcAsset ) )
+                {
+
+                    if (!destAsset.exists() && srcAsset.isContainer()) {
+                        destAsset.create( AssetType.CONTAINER );
                     }
-                } ).allMatch( r -> r );
+                    return stream.map( a -> {
+                        try
+                        {
+                            String relativePath = destAsset.relativize( a );
+                            System.out.println( "Destination relative: " + relativePath );
+                            StorageAsset destFile = destAsset.resolve( relativePath );
+                            assert destFile != null;
+                            System.out.println( "Destination " + destFile.getPath( ) + " " + a.isContainer() );
+                            if (a.isContainer()) {
+                                destFile.create( AssetType.CONTAINER);
+                            } else {
+                                if (!destFile.getParent( ).exists() ) {
+                                    System.out.println( "Creating parent " + destFile.getParent( ) );
+                                    destFile.getParent().create( AssetType.CONTAINER );
+                                }
+                                System.out.println( "Copying " + a.getPath( ) + "->" + destFile.getPath( ) );
+                                copy( a.getReadChannel( ), destFile.getWriteChannel( true ) );
+                            }
+                            return Boolean.TRUE;
+                        }
+                        catch ( IOException e )
+                        {
+                            LOG.error( "Could not copy asset {}: {}", a.getPath( ), e.getMessage( ), e );
+                            // Returning true, if exception
+                            if ( stopOnError )
+                            {
+                                throw new RuntimeException( e );
+                            }
+                            else
+                            {
+                                return Boolean.FALSE;
+                            }
+                        }
+                    } ).reduce( ( a, b ) -> Boolean.logicalAnd( a, b ) ).orElse(Boolean.FALSE);
+                }
             }
+        } catch (RuntimeException e) {
+            System.err.println( "Exception " + e.getMessage( ) );
+            e.printStackTrace( );
+            return false;
         }
     }
 
index 0e19459f0a6aece4e2b717ad2f20b5b81cbd0392..c2d38338e8e92d6725b85ff60cf5d65a2336d480 100644 (file)
@@ -19,21 +19,29 @@ package org.apache.archiva.repository.storage.mock;
  * under the License.
  */
 
+import org.apache.archiva.repository.storage.AssetType;
 import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
+import org.apache.archiva.repository.storage.util.StorageUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+
+import static org.mockito.Matchers.any;
 
 public class MockAsset implements StorageAsset
 {
@@ -43,24 +51,37 @@ public class MockAsset implements StorageAsset
     private LinkedHashMap<String, MockAsset> children = new LinkedHashMap<>( );
     private boolean container = false;
     private RepositoryStorage storage;
-
+    private boolean exists = false;
 
 
     private boolean throwException;
 
     public MockAsset( String name ) {
+        this.parent = null;
         this.name = name;
         this.path = "/";
     }
 
     public MockAsset( MockAsset parent, String name ) {
+        if (parent!=null && "".equals(name)) {
+            throw new RuntimeException( "Bad asset creation with empty name and parent" );
+        }
         this.parent = parent;
-        this.path = (parent.hasParent()?parent.getPath( ):"") + "/" + name;
+        this.path = getPath( parent, name );
         this.name = name;
         this.storage = parent.getStorage( );
         parent.registerChild( this );
     }
 
+    private String getPath(MockAsset parent, String name) {
+
+        if (parent.hasParent() && !parent.getPath(  ).equals( "/" )) {
+            return parent.getPath( ) + "/" + name;
+        } else {
+            return "/" + name;
+        }
+    }
+
     public void registerChild(MockAsset child) {
         children.putIfAbsent( child.getName(), child );
         this.container = true;
@@ -136,25 +157,28 @@ public class MockAsset implements StorageAsset
     @Override
     public InputStream getReadStream( ) throws IOException
     {
-        return null;
+        return Mockito.mock( InputStream.class );
     }
 
     @Override
     public ReadableByteChannel getReadChannel( ) throws IOException
     {
-        return null;
+        ReadableByteChannel channel = Mockito.mock( ReadableByteChannel.class );
+        Mockito.when( channel.read( any( ByteBuffer.class ) ) ).thenReturn( -1 );
+        return channel;
     }
 
     @Override
     public OutputStream getWriteStream( boolean replace ) throws IOException
     {
-        return null;
+        return Mockito.mock( OutputStream.class );
     }
 
     @Override
     public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
     {
-        return null;
+        this.exists = true;
+        return Mockito.mock( WritableByteChannel.class );
     }
 
     @Override
@@ -166,13 +190,22 @@ public class MockAsset implements StorageAsset
     @Override
     public boolean exists( )
     {
-        return false;
+        return exists;
     }
 
     @Override
     public void create( ) throws IOException
     {
+        this.exists = true;
+    }
 
+    @Override
+    public void create( AssetType type ) throws IOException
+    {
+        if (type.equals( AssetType.CONTAINER )) {
+            this.container = true;
+        }
+        this.exists = true;
     }
 
     @Override
@@ -205,7 +238,39 @@ public class MockAsset implements StorageAsset
         if (children.containsKey( toPath )) {
             return children.get( toPath );
         } else {
-            return null;
+            if (toPath.startsWith( "/" )) {
+                toPath = StringUtils.removeStart( toPath, "/" );
+            }
+            if ( "".equals( toPath ) )
+            {
+                return this;
+            }
+            String[] destPath = toPath.split( "/" );
+            StringBuilder destPathStr = new StringBuilder( );
+            MockAsset destParent = this;
+            for (int i=0; i<destPath.length; i++) {
+                destPathStr.append( "/" ).append( destPath[i] );
+                StorageAsset child = storage.getAsset( destPathStr.toString( ) );
+                if (child!=null) {
+                    destParent = (MockAsset) child;
+                } else
+                {
+                    System.out.println( "Resolve " + destParent.getPath( ) + " -- " + destPath[i] );
+                    destParent = new MockAsset( destParent, destPath[i] );
+                }
+            }
+            return destParent;
+        }
+    }
+
+    @Override
+    public String relativize( StorageAsset asset )
+    {
+        System.out.println( "relativize this " + this.getPath( ) + " -> other " + asset.getPath( ) );
+        if (asset.isFileBased()) {
+            return Paths.get( getPath( ) ).relativize( asset.getFilePath( ) ).toString();
+        } else {
+            return StringUtils.removeStart( asset.getPath( ), this.getPath( ) );
         }
     }
 
index e024caefb0282ad4aec08510e9c87417188a6a3c..23acc89caeec29a53da52e8cb3ad094390e47c0a 100644 (file)
@@ -18,6 +18,7 @@ package org.apache.archiva.repository.storage.mock;
  * under the License.
  */
 
+import org.apache.archiva.repository.storage.AssetType;
 import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.archiva.repository.storage.util.VisitStatus;
@@ -48,12 +49,30 @@ public class MockStorage implements RepositoryStorage
     public MockStorage( MockAsset root )
     {
         this.root = root;
+        try
+        {
+            this.root.create( AssetType.CONTAINER );
+        }
+        catch ( IOException e )
+        {
+            e.printStackTrace( );
+        }
         root.setStorage( this );
+        assets.put( "/", root );
     }
 
     public MockStorage() {
-        this.root = new MockAsset( "" );
+        this.root = new MockAsset( "/" );
+        try
+        {
+            this.root.create( AssetType.CONTAINER );
+        }
+        catch ( IOException e )
+        {
+            e.printStackTrace( );
+        }
         this.root.setStorage( this );
+        assets.put( "/", this.root );
     }
 
     public VisitStatus getStatus() {
@@ -85,8 +104,9 @@ public class MockStorage implements RepositoryStorage
     }
 
     @Override
-    public StorageAsset getAsset( String path )
+    public StorageAsset getAsset( final String requestedPath )
     {
+        String path =  requestedPath.startsWith( "/" ) ? requestedPath : "/"+requestedPath;
         if (assets.containsKey( path )) {
             return assets.get( path );
         }
diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AbstractStorageUtilTest.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AbstractStorageUtilTest.java
new file mode 100644 (file)
index 0000000..9a15a08
--- /dev/null
@@ -0,0 +1,234 @@
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * 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.
+ */
+
+import org.apache.archiva.repository.storage.AssetType;
+import org.apache.archiva.repository.storage.RepositoryStorage;
+import org.apache.archiva.repository.storage.StorageAsset;
+import org.apache.archiva.repository.storage.mock.MockStorage;
+import org.apache.archiva.repository.storage.util.StorageUtil;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Abstract test class for the storage utility to test for different storage implementations.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public abstract class AbstractStorageUtilTest
+{
+    private static final int LEVEL1 = 12;
+    private static final int LEVEL2 = 13;
+    private static final int LEVEL3 = 6;
+
+    /**
+     * A subclass must override this method. This method returns a new asset instance with the given parent.
+     *
+     * @param parent the parent asset for the newly created asset
+     * @param name   the name of the new asset
+     * @return the asset
+     */
+    protected abstract StorageAsset createAsset( StorageAsset parent, String name, AssetType type );
+
+    protected StorageAsset createAsset(StorageAsset parent, String name) {
+        return createAsset( parent, name, AssetType.FILE );
+    }
+
+    /**
+     * A subclass must override this method. This method returns a new root asset instance without parent.
+     * @return the newly created asset instance
+     */
+    protected abstract StorageAsset createRootAsset( );
+
+    /**
+     * Activates a exception on a certain asset in the storage
+     * @param root the root asset
+     */
+    protected abstract void activateException( StorageAsset root );
+
+
+
+    /**
+     * A subclass should override this method. This method creates a new storage instance with the given root element.
+     *
+     * @param root the root asset
+     * @return the storage instance
+     */
+    protected abstract RepositoryStorage createStorage( StorageAsset root );
+
+
+    protected StorageAsset createTree( )
+    {
+        return createTree( LEVEL1, LEVEL2, LEVEL3 );
+    }
+
+    protected StorageAsset createTree( int... levelElements )
+    {
+        StorageAsset root = createRootAsset( );
+        recurseSubTree( root, 0, levelElements );
+        return root;
+    }
+
+    private void recurseSubTree( StorageAsset parent, int level, int[] levelElements )
+    {
+        if ( level < levelElements.length )
+        {
+            AssetType type = ( level == levelElements.length - 1 ) ? AssetType.FILE : AssetType.CONTAINER;
+            for ( int k = 0; k < levelElements[level]; k++ )
+            {
+                String name = parent.getName( ) + String.format( "%03d", k );
+                StorageAsset asset = createAsset( parent, name, type );
+                recurseSubTree( asset, level + 1, levelElements );
+            }
+        }
+    }
+
+    @Test
+    void testWalkFromRoot( )
+    {
+        StorageAsset root = createTree( );
+        ConsumeVisitStatus status = new ConsumeVisitStatus( );
+
+        StorageUtil.walk( root, status );
+        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+        Assertions.assertEquals( expected, status.size( ) );
+        StorageAsset first = root.list( ).get( 0 ).list( ).get( 0 ).list( ).get( 0 );
+        Assertions.assertEquals( first, status.getFirst( ) );
+        Assertions.assertEquals( root, status.getLast( ) );
+    }
+
+    @Test
+    void testWalkFromChild( )
+    {
+        StorageAsset root = createTree( );
+        ConsumeVisitStatus status = new ConsumeVisitStatus( );
+        StorageAsset testRoot = root.list( ).get( 3 );
+
+        StorageUtil.walk( testRoot, status );
+        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+        Assertions.assertEquals( expected, status.size( ) );
+        StorageAsset first = root.list( ).get( 3 ).list( ).get( 0 ).list( ).get( 0 );
+        Assertions.assertEquals( first, status.getFirst( ) );
+        Assertions.assertEquals( testRoot, status.getLast( ) );
+    }
+
+    @Test
+    void testWalkFromRootWithCondition( )
+    {
+        StorageAsset root = createTree( );
+        StopVisitStatus status = new StopVisitStatus( );
+        status.setStopCondition( a -> a.getName( ).equals( "001002003" ) );
+
+        StorageUtil.walk( root, status );
+        Assertions.assertEquals( "001002003", status.getLast( ).getName( ) );
+        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 2 * LEVEL3 + 1 + 1 + 1 + 4;
+        Assertions.assertEquals( expected, status.size( ) );
+    }
+
+    @Test
+    void testStream( )
+    {
+        StorageAsset root = createTree( );
+        List<StorageAsset> result;
+        try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, false ) )
+        {
+            result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList( ) );
+        }
+        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+        Assertions.assertEquals( expected, result.size( ) );
+        Assertions.assertEquals( "001", result.get( result.size( ) - 1 ).getName( ) );
+        Assertions.assertEquals( "001012", result.get( result.size( ) - 2 ).getName( ) );
+    }
+
+    @Test
+    void testStreamParallel( )
+    {
+        StorageAsset root = createTree( );
+        List<StorageAsset> result;
+        try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, true ) )
+        {
+            result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList( ) );
+        }
+        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+        Assertions.assertEquals( expected, result.size( ) );
+    }
+
+    @Test
+    void testDelete( )
+    {
+        StorageAsset root = createTree( );
+        RepositoryStorage storage = createStorage( root );
+
+        StorageUtil.deleteRecursively( root );
+        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+        testDeletionStatus( expected, storage );
+
+    }
+
+    protected abstract void testDeletionStatus( int expected, RepositoryStorage storage );
+
+    @Test
+    void testDeleteWithException( )
+    {
+        StorageAsset root = createTree( );
+        RepositoryStorage storage = createStorage( root );
+        activateException( root );
+
+        StorageUtil.deleteRecursively( root );
+        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+        testDeletionStatus( expected, storage );
+    }
+
+    @Test
+    void testDeleteWithExceptionFailFast( )
+    {
+        StorageAsset root = createTree( );
+        RepositoryStorage storage = createStorage( root );
+        activateException( root );
+
+        StorageUtil.deleteRecursively( root, true );
+        int expected = 113;
+        testDeletionStatus( expected, storage );
+    }
+
+    @Test
+    void testCopyRecursive( ) throws IOException
+    {
+        StorageAsset root = createTree( );
+        createStorage( root );
+        StorageAsset destinationRoot = createRootAsset( );
+        RepositoryStorage destinationStorage = createStorage( destinationRoot );
+        StorageAsset destination = destinationStorage.getAsset( "" );
+        boolean result = StorageUtil.copyRecursively( root, destination, false );
+        Assertions.assertTrue( result );
+        Assertions.assertTrue( destination.exists( ) );
+        Assertions.assertTrue( destination.resolve( "000/000000/000000000" ).exists( ) );
+        Assertions.assertTrue( destination.resolve( "011/011000/011000000" ).exists( ) );
+        Assertions.assertTrue( destination.resolve( "010/010000/010000000" ).exists( ) );
+        Assertions.assertTrue( destination.resolve( "000/000000/000000000" ).exists( ) );
+
+    }
+}
index c1c53c7e231192af8035965b61e513e091806362..8d6fbef727f42402dc2f267ea29e49918a535a64 100644 (file)
@@ -19,157 +19,71 @@ package org.apache.archiva.repository.storage.util;
  */
 
 
+import org.apache.archiva.repository.storage.AssetType;
+import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.archiva.repository.storage.mock.MockAsset;
 import org.apache.archiva.repository.storage.mock.MockStorage;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Assertions;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
  */
-class StorageUtilTest
+class StorageUtilTest extends AbstractStorageUtilTest
 {
-    private static int LEVEL1 = 12;
-    private static int LEVEL2 = 13;
-    private static int LEVEL3 = 6;
-
 
 
-    private MockAsset createTree() {
-        return createTree( LEVEL1, LEVEL2, LEVEL3 );
-    }
-
-    private MockAsset createTree(int... levelElements) {
-        MockAsset root = new MockAsset( "" );
-        recurseSubTree( root, 0, levelElements );
-        return root;
+    private MockStorage createStorage(MockAsset root) {
+        return new MockStorage( root );
     }
 
-    private void recurseSubTree(MockAsset parent, int level, int[] levelElements) {
-        if (level < levelElements.length)
+    private MockAsset createAsset( MockAsset parent, String name, AssetType type ) {
+        if (parent==null) {
+            return new MockAsset( name );
+        } else
         {
-            for ( int k = 0; k < levelElements[level]; k++ )
-            {
-                String name = parent.getName( ) + String.format( "%03d", k );
-                MockAsset asset = new MockAsset( parent, name );
-                recurseSubTree( asset, level + 1, levelElements );
-            }
+            return new MockAsset( parent, name );
         }
     }
 
-    @Test
-    void testWalkFromRoot() {
-        StorageAsset root = createTree( );
-        ConsumeVisitStatus status = new ConsumeVisitStatus( );
 
-        StorageUtil.walk( root, status );
-        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
-        assertEquals( expected, status.size() );
-        StorageAsset first = root.list( ).get( 0 ).list( ).get( 0 ).list().get(0);
-        assertEquals( first, status.getFirst( ) );
-        assertEquals( root, status.getLast( ) );
-    }
-
-    @Test
-    void testWalkFromChild() {
-        StorageAsset root = createTree( );
-        ConsumeVisitStatus status = new ConsumeVisitStatus( );
-        StorageAsset testRoot = root.list( ).get( 3 );
-
-        StorageUtil.walk( testRoot, status );
-        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
-        assertEquals( expected, status.size() );
-        StorageAsset first = root.list( ).get( 3 ).list( ).get( 0 ).list().get(0);
-        assertEquals( first, status.getFirst( ) );
-        assertEquals( testRoot, status.getLast( ) );
-    }
-
-
-    @Test
-    void testWalkFromRootWithCondition() {
-        StorageAsset root = createTree( );
-        StopVisitStatus status = new StopVisitStatus( );
-        status.setStopCondition( a -> a.getName().equals("001002003") );
-
-        StorageUtil.walk( root, status );
-        assertEquals( "001002003", status.getLast( ).getName() );
-        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 2 * LEVEL3 + 1 + 1 + 1 + 4;
-        assertEquals( expected, status.size() );
+    protected void activateException(MockAsset root) {
+        root.list( ).get( 1 ).list( ).get( 2 ).setThrowException( true );
     }
 
-    @Test
-    void testStream() {
-        StorageAsset root = createTree( );
-        ConsumeVisitStatus status = new ConsumeVisitStatus( );
-
-        List<StorageAsset> result;
-        try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, false ) )
-        {
-            result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList());
-        }
-        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
-        assertEquals( expected, result.size( ) );
-        assertEquals( "001", result.get( result.size( ) - 1 ).getName() );
-        assertEquals( "001012", result.get( result.size( ) - 2 ).getName() );
+    @Override
+    protected StorageAsset createAsset( StorageAsset parent, String name, AssetType type )
+    {
+        return createAsset( (MockAsset) parent, name, type);
     }
 
-    @Test
-    void testStreamParallel() {
-        StorageAsset root = createTree( );
-        ConsumeVisitStatus status = new ConsumeVisitStatus( );
-
-        List<StorageAsset> result;
-        try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, true ) )
-        {
-            result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList());
-        }
-        int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
-        assertEquals( expected, result.size( ) );
+    @Override
+    protected StorageAsset createRootAsset( )
+    {
+        return new MockAsset( "" );
     }
 
-
-    @Test
-    void testDelete() throws IOException
+    @Override
+    protected void activateException( StorageAsset root )
     {
-        MockAsset root = createTree( );
-        MockStorage storage = new MockStorage( root );
-
-        StorageUtil.deleteRecursively( root );
-        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
-        assertEquals( expected, storage.getStatus( ).size( MockStorage.REMOVE ) );
-
+        activateException( (MockAsset)root );
     }
 
-    @Test
-    void testDeleteWithException() throws IOException
+    @Override
+    protected RepositoryStorage createStorage( StorageAsset root )
     {
-        MockAsset root = createTree( );
-        MockStorage storage = new MockStorage( root );
-        root.list( ).get( 1 ).list( ).get( 2 ).setThrowException( true );
-
-        StorageUtil.deleteRecursively( root );
-        int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
-        assertEquals( expected, storage.getStatus( ).size( MockStorage.REMOVE ) );
-
+        return new MockStorage( (MockAsset) root );
     }
 
-    @Test
-    void testDeleteWithExceptionFailFast() throws IOException
+    protected void testDeletionStatus( int expected, RepositoryStorage storage )
     {
-        MockAsset root = createTree( );
-        MockStorage storage = new MockStorage( root );
-        root.list( ).get( 1 ).list( ).get( 2 ).setThrowException( true );
-
-        StorageUtil.deleteRecursively( root, true );
-        int expected = 113;
-        assertEquals( expected, storage.getStatus( ).size( MockStorage.REMOVE ) );
-
+        if ( storage instanceof MockStorage )
+        {
+            Assertions.assertEquals( expected, ( (MockStorage) storage ).getStatus( ).size( MockStorage.REMOVE ) );
+        }
+        else
+        {
+            Assertions.fail( "Deletion status not implemented for this storage " + storage.getClass( ).getName( ) );
+        }
     }
 }
\ No newline at end of file
index f47d9d7aa9cbce3d0ca50bd9b32346e17196852e..c277b6382ee4ee7d084c3c838cc1d562f8d11f85 100644 (file)
       <artifactId>slf4j-api</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.archiva</groupId>
+      <artifactId>archiva-storage-api</artifactId>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+      <version>${project.version}</version>
+    </dependency>
+
   </dependencies>
 
   <build>
index fb118a85c4d85462f29647c22cf101be0be710e5..1bbbe5b7224ec6c78d2c3a37e5fa548c8b63e331 100644 (file)
@@ -18,12 +18,14 @@ package org.apache.archiva.repository.storage.fs;
  * under the License.
  */
 
+import org.apache.archiva.repository.storage.AssetType;
 import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -89,7 +91,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
     boolean supportsAcl = false;
     boolean supportsPosix = false;
     final boolean setPermissionsForNew;
-    final RepositoryStorage storage;
+    final FilesystemStorage storage;
 
     boolean directoryHint = false;
 
@@ -97,7 +99,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
     private static final OpenOption[] APPEND_OPTIONS = new OpenOption[]{StandardOpenOption.APPEND};
 
 
-    FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath) {
+    FilesystemAsset(FilesystemStorage storage, String path, Path assetPath, Path basePath) {
         this.assetPath = assetPath;
         this.relativePath = normalizePath(path);
         this.setPermissionsForNew=false;
@@ -113,7 +115,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
      * @param path The logical path for the asset relative to the repository.
      * @param assetPath The asset path.
      */
-    public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath) {
+    public FilesystemAsset(FilesystemStorage storage, String path, Path assetPath) {
         this.assetPath = assetPath;
         this.relativePath = normalizePath(path);
         this.setPermissionsForNew = false;
@@ -135,7 +137,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
      * @param directory This is only relevant, if the represented file or directory does not exist yet and
      *                  is a hint.
      */
-    public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory) {
+    public FilesystemAsset(FilesystemStorage storage, String path, Path assetPath, Path basePath, boolean directory) {
         this.assetPath = assetPath;
         this.relativePath = normalizePath(path);
         this.directoryHint = directory;
@@ -154,7 +156,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
      * @param directory This is only relevant, if the represented file or directory does not exist yet and
      *                  is a hint.
      */
-    public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory, boolean setPermissionsForNew) {
+    public FilesystemAsset(FilesystemStorage storage, String path, Path assetPath, Path basePath, boolean directory, boolean setPermissionsForNew) {
         this.assetPath = assetPath;
         this.relativePath = normalizePath(path);
         this.directoryHint = directory;
@@ -231,7 +233,7 @@ public class FilesystemAsset implements StorageAsset, Comparable {
 
     @Override
     public String getName() {
-        return assetPath.getFileName().toString();
+        return hasParent( ) ? assetPath.getFileName( ).toString( ) : "";
     }
 
     @Override
@@ -277,7 +279,9 @@ public class FilesystemAsset implements StorageAsset, Comparable {
     @Override
     public List<StorageAsset> list() {
         try {
-            return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p), this.basePath))
+            return Files.list(assetPath)
+                .sorted()
+                .map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p), this.basePath))
                     .collect(Collectors.toList());
         } catch (IOException e) {
             return Collections.EMPTY_LIST;
@@ -505,6 +509,37 @@ public class FilesystemAsset implements StorageAsset, Comparable {
         }
     }
 
+    @Override
+    public void create( AssetType type ) throws IOException {
+        if (!Files.exists(assetPath)) {
+            if (type.equals( AssetType.CONTAINER ) || directoryHint) {
+                Files.createDirectories(assetPath);
+            } else {
+                if (!Files.exists( assetPath.getParent() )) {
+                    Files.createDirectories( assetPath.getParent( ) );
+                }
+                Files.createFile(assetPath);
+            }
+            if (setPermissionsForNew) {
+                applyDefaultPermissions(assetPath);
+            }
+        }
+    }
+
+    @Override
+    public String relativize( StorageAsset asset )
+    {
+        if (asset instanceof FilesystemAsset ) {
+            return this.relativize( (FilesystemAsset) asset );
+        } else {
+            return StringUtils.removeStart( asset.getPath( ), this.getPath( ) );
+        }
+    }
+
+    public String relativize( FilesystemAsset asset) {
+        return this.getFilePath( ).relativize( asset.getFilePath( ) ).toString();
+    }
+
     @Override
     public String toString() {
         return relativePath+":"+assetPath;
diff --git a/archiva-modules/archiva-base/archiva-storage-fs/src/test/java/org/apache/archiva/repository/storage/fs/FileSystemStorageUtilTest.java b/archiva-modules/archiva-base/archiva-storage-fs/src/test/java/org/apache/archiva/repository/storage/fs/FileSystemStorageUtilTest.java
new file mode 100644 (file)
index 0000000..f403489
--- /dev/null
@@ -0,0 +1,120 @@
+package org.apache.archiva.repository.storage.fs;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.repository.storage.AssetType;
+import org.apache.archiva.repository.storage.RepositoryStorage;
+import org.apache.archiva.repository.storage.StorageAsset;
+import org.apache.archiva.repository.storage.util.AbstractStorageUtilTest;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.TestInstance;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@TestInstance( TestInstance.Lifecycle.PER_CLASS)
+public class FileSystemStorageUtilTest extends AbstractStorageUtilTest
+{
+
+    List<Path> tmpDirs = new ArrayList<>( );
+
+    @Override
+    protected StorageAsset createAsset( StorageAsset parent, String name, AssetType type )
+    {
+        if (parent instanceof FilesystemAsset) {
+            return createAsset( (FilesystemAsset) parent, name, type );
+        } else {
+            fail( "Bad asset instance type" );
+            return null;
+        }
+    }
+
+    @AfterAll
+    private void cleanup() {
+        for( Path dir : tmpDirs) {
+            if (Files.exists( dir )) {
+                FileUtils.deleteQuietly( dir.toFile( ) );
+            }
+        }
+    }
+
+    private FilesystemAsset createAsset(FilesystemAsset parent, String name, AssetType type) {
+        FilesystemAsset asset = (FilesystemAsset) parent.resolve( name );
+        try
+        {
+            asset.create(type);
+            return asset;
+        }
+        catch ( IOException e )
+        {
+            fail( "Could not create asset " + e.getMessage( ) );
+            return null;
+        }
+    }
+
+    @Override
+    protected StorageAsset createRootAsset( )
+    {
+        try
+        {
+            Path tmpDir = Files.createTempDirectory( "testfs" );
+            tmpDirs.add( tmpDir );
+            FilesystemStorage storage = new FilesystemStorage( tmpDir, new DefaultFileLockManager( ) );
+            return storage.getRoot( );
+        }
+        catch ( IOException e )
+        {
+            fail( "Could not create storage" );
+            return null;
+        }
+    }
+
+    @Override
+    protected void activateException( StorageAsset root )
+    {
+        // Not done here
+    }
+
+    @Override
+    protected RepositoryStorage createStorage( StorageAsset root )
+    {
+        if (root instanceof FilesystemAsset) {
+            return root.getStorage( );
+        } else {
+            fail( "Wrong asset implementation " + root.getClass( ).getName( ) );
+            return null;
+        }
+    }
+
+    @Override
+    protected void testDeletionStatus( int expected, RepositoryStorage storage )
+    {
+        assertFalse( Files.exists( storage.getRoot( ).getFilePath( ) ) );
+    }
+}
index fe994429ef1dd52092afc1d39f33ef503b7b6dd5..237b9046b0ef431af3bf222ef14e0b51892fe061 100644 (file)
@@ -135,6 +135,18 @@ public class ManagedRepositoryContentMock implements BaseRepositoryContentLayout
 
     }
 
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository, boolean updateMetadata ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
     @Override
     public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
     {
index a9815db83980c8bb46f00242e71a973b5d44d225..b0c7b5e2e05b44a8701e94897ca6e90912187135 100644 (file)
@@ -286,6 +286,18 @@ public class ManagedDefaultRepositoryContent
         }
     }
 
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
+    @Override
+    public void copyItem( ContentItem item, ManagedRepository destinationRepository, boolean updateMetadata ) throws ItemNotFoundException, ContentAccessException
+    {
+
+    }
+
     @Override
     public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
     {
index e1f423d2b3f8bb2ca397a51d1e24771cea4819c8..d5612c6f396fc8d970d7b09e9c1d078c6269ce9b 100644 (file)
@@ -35,9 +35,11 @@ package org.apache.archiva.rest.api.model.v2;/*
  */
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.repository.storage.StorageAsset;
 
 import java.io.Serializable;
 import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -50,7 +52,19 @@ public class FileInfo implements Serializable
     private String fileName;
     private String path;
 
-    @Schema(description = "Time when the file was last modified")
+    public FileInfo( )
+    {
+    }
+
+    public static FileInfo of( StorageAsset asset ) {
+        FileInfo fileInfo = new FileInfo( );
+        fileInfo.setFileName( asset.getName() );
+        fileInfo.setPath( asset.getPath() );
+        fileInfo.setModified( asset.getModificationTime( ).atOffset( ZoneOffset.UTC ) );
+        return fileInfo;
+    }
+
+                               @Schema(description = "Time when the file was last modified")
     public OffsetDateTime getModified( )
     {
         return modified;
index 6a045b4386fff247f865fe75fa5b271806ac4a2c..99455f148f2e3995df05dfd5d07fea2809a10cab 100644 (file)
@@ -35,9 +35,17 @@ package org.apache.archiva.rest.api.model.v2;/*
  */
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.features.ArtifactCleanupFeature;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.features.StagingRepositoryFeature;
 
+import java.time.Period;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -49,6 +57,52 @@ public class MavenManagedRepository extends Repository
 
     boolean blocksRedeployments;
     List<String> releaseSchemes = new ArrayList<>(  );
+    boolean deleteSnapshotsOfRelease = false;
+    private Period retentionPeriod;
+    private int retentionCount;
+    private String indexPath;
+    private String packedIndexPath;
+    private boolean skipPackedIndexCreation;
+    private boolean hasStagingRepository;
+    private String stagingRepository;
+
+
+    public MavenManagedRepository( )
+    {
+        super.setCharacteristic( Repository.CHARACTERISTIC_MANAGED );
+        super.setType( RepositoryType.MAVEN.name( ) );
+    }
+
+    protected static void update(MavenManagedRepository repo, ManagedRepository beanRepo) {
+        repo.setDescription( beanRepo.getDescription() );
+        repo.setId( beanRepo.getId() );
+        repo.setIndex( true );
+        repo.setLayout( beanRepo.getLayout() );
+        repo.setBlocksRedeployments( beanRepo.blocksRedeployments() );
+        repo.setReleaseSchemes( beanRepo.getActiveReleaseSchemes().stream().map( Objects::toString).collect( Collectors.toList()) );
+        repo.setLocation( beanRepo.getLocation().toString() );
+        repo.setName( beanRepo.getName());
+        repo.setScanned( beanRepo.isScanned() );
+        repo.setSchedulingDefinition( beanRepo.getSchedulingDefinition() );
+        ArtifactCleanupFeature artifactCleanupFeature = beanRepo.getFeature( ArtifactCleanupFeature.class ).get( );
+        repo.setDeleteSnapshotsOfRelease( artifactCleanupFeature.isDeleteReleasedSnapshots());
+        repo.setRetentionCount( artifactCleanupFeature.getRetentionCount());
+        repo.setRetentionPeriod( artifactCleanupFeature.getRetentionPeriod() );
+        IndexCreationFeature icf = beanRepo.getFeature( IndexCreationFeature.class ).get( );
+        repo.setIndex( icf.hasIndex( ) );
+        repo.setIndexPath( icf.getIndexPath( ).getPath( ) );
+        repo.setPackedIndexPath( icf.getPackedIndexPath( ).getPath( ) );
+        repo.setSkipPackedIndexCreation( icf.isSkipPackedIndexCreation() );
+        StagingRepositoryFeature srf = beanRepo.getFeature( StagingRepositoryFeature.class ).get( );
+        repo.setHasStagingRepository( srf.isStageRepoNeeded( ) );
+        repo.setStagingRepository( srf.getStagingRepository()!=null?srf.getStagingRepository().getId():"" );
+    }
+
+    public static MavenManagedRepository of( ManagedRepository beanRepo ) {
+        MavenManagedRepository repo = new MavenManagedRepository( );
+        update( repo, beanRepo );
+        return repo;
+    }
 
     @Schema(name="blocks_redeployments",description = "True, if redeployments to this repository are not allowed")
     public boolean isBlocksRedeployments( )
@@ -72,6 +126,101 @@ public class MavenManagedRepository extends Repository
         this.releaseSchemes = new ArrayList<>( releaseSchemes );
     }
 
+    public void addReleaseScheme(String scheme) {
+        if (!this.releaseSchemes.contains( scheme ))
+        {
+            this.releaseSchemes.add( scheme );
+        }
+    }
+
+    @Schema(name="delete_snaphots_of_release", description = "True, if snapshots are deleted, after a version is released")
+    public boolean isDeleteSnapshotsOfRelease( )
+    {
+        return deleteSnapshotsOfRelease;
+    }
+
+    public void setDeleteSnapshotsOfRelease( boolean deleteSnapshotsOfRelease )
+    {
+        this.deleteSnapshotsOfRelease = deleteSnapshotsOfRelease;
+    }
+
+    @Schema(name="retention_period", description = "The period after which snapshots are deleted.")
+    public Period getRetentionPeriod( )
+    {
+        return retentionPeriod;
+    }
+
+    public void setRetentionPeriod( Period retentionPeriod )
+    {
+        this.retentionPeriod = retentionPeriod;
+    }
+
+    @Schema(name="retention_count", description = "Number of snapshot artifacts to keep.")
+    public int getRetentionCount( )
+    {
+        return retentionCount;
+    }
+
+    public void setRetentionCount( int retentionCount )
+    {
+        this.retentionCount = retentionCount;
+    }
+
+    @Schema( name = "index_path", description = "Path to the directory that contains the index, relative to the repository base directory" )
+    public String getIndexPath( )
+    {
+        return indexPath;
+    }
+
+    public void setIndexPath( String indexPath )
+    {
+        this.indexPath = indexPath;
+    }
+
+    @Schema( name = "packed_index_path", description = "Path to the directory that contains the packed index, relative to the repository base directory" )
+    public String getPackedIndexPath( )
+    {
+        return packedIndexPath;
+    }
+
+    public void setPackedIndexPath( String packedIndexPath )
+    {
+        this.packedIndexPath = packedIndexPath;
+    }
+
+    @Schema(name="skip_packed_index_creation", description = "True, if packed index is not created during index update")
+    public boolean isSkipPackedIndexCreation( )
+    {
+        return skipPackedIndexCreation;
+    }
+
+    public void setSkipPackedIndexCreation( boolean skipPackedIndexCreation )
+    {
+        this.skipPackedIndexCreation = skipPackedIndexCreation;
+    }
+
+    @Schema(name="has_staging_repository", description = "True, if this repository has a staging repository assigned")
+    public boolean isHasStagingRepository( )
+    {
+        return hasStagingRepository;
+    }
+
+    public void setHasStagingRepository( boolean hasStagingRepository )
+    {
+        this.hasStagingRepository = hasStagingRepository;
+    }
+
+    @Schema(name="staging_repository", description = "The id of the assigned staging repository")
+    public String getStagingRepository( )
+    {
+        return stagingRepository;
+    }
+
+    public void setStagingRepository( String stagingRepository )
+    {
+        this.stagingRepository = stagingRepository;
+    }
+
     @Override
     public boolean equals( Object o )
     {
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/MavenManagedRepositoryUpdate.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/MavenManagedRepositoryUpdate.java
new file mode 100644 (file)
index 0000000..32809f6
--- /dev/null
@@ -0,0 +1,47 @@
+package org.apache.archiva.rest.api.model.v2;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.repository.ManagedRepository;
+
+import java.io.Serializable;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class MavenManagedRepositoryUpdate extends MavenManagedRepository implements Serializable
+{
+    private static final long serialVersionUID = -9181643343284109862L;
+    private boolean resetStats = false;
+
+    public static MavenManagedRepositoryUpdate of( ManagedRepository repository ) {
+        MavenManagedRepositoryUpdate repo = new MavenManagedRepositoryUpdate( );
+        update( repo, repository );
+        return repo;
+    }
+
+    public boolean isResetStats( )
+    {
+        return resetStats;
+    }
+
+    public void setResetStats( boolean resetStats )
+    {
+        this.resetStats = resetStats;
+    }
+}
index 793f2d7d10429b7cbbada238bdcbc6a152e2f8d3..ab74ceaecc7c46bf077b63d72a55eeefb04bf9fc 100644 (file)
@@ -78,8 +78,48 @@ public interface ErrorKeys
     String TASK_QUEUE_FAILED = PREFIX + "task.queue_failed";
     String REPOSITORY_SCAN_FAILED = REPOSITORY_PREFIX + "scan.failed";
     String ARTIFACT_EXISTS_AT_DEST = REPOSITORY_PREFIX + "artifact.dest.exists";
-
     String REPOSITORY_REMOTE_INDEX_DOWNLOAD_FAILED = REPOSITORY_PREFIX + "remote.index.download_failed";
+    String REPOSITORY_WRONG_TYPE = REPOSITORY_PREFIX + "wrong_type";
+    String REPOSITORY_DELETE_FAILED = REPOSITORY_PREFIX + "delete.failed";
+    String REPOSITORY_INVALID_ID = REPOSITORY_PREFIX + "invalid.id";
+    String REPOSITORY_ID_EXISTS = REPOSITORY_PREFIX + "id.exists";
+    String REPOSITORY_UPDATE_FAILED = REPOSITORY_PREFIX + "update.failed";
+    String ARTIFACT_NOT_FOUND = REPOSITORY_PREFIX + "artifact.notfound";
+    String REPOSITORY_LAYOUT_ERROR = REPOSITORY_PREFIX + "layout.error";
+
+    String ARTIFACT_COPY_ERROR = REPOSITORY_PREFIX + "artifact.copy.error";
+
+    /**
+     * The given user was not found
+     * Parameters:
+     * - User Id
+     */
+    String USER_NOT_FOUND = PREFIX+"user.not_found";
+
+    /**
+     * Error from UserManager
+     * Parameters:
+     * - Error Message
+     */
+    String USER_MANAGER_ERROR = PREFIX+"user_manager.error";
+
+    /**
+     * Permission to the repository denied.
+     * Parameters:
+     * - Repository Id
+     * - Permission ID
+     */
+    String PERMISSION_REPOSITORY_DENIED = PREFIX + "permission.repository.denied";
 
+    /**
+     * A generic authorization error thrown during the authorization check.
+     * Parameters:
+     * - Error message
+     */
+    String AUTHORIZATION_ERROR = PREFIX + "authorization.error";
 
+    /**
+     * When the operation needs authentication, but not authenticated user was found in the request context.
+     */
+    String NOT_AUTHENTICATED = PREFIX + "user.not_authenticated";
 }
index dd931217c191668bd9db3d743927177ca5202923..5aff95ef66f65ba05e93e2ef1bec1a2808e195cc 100644 (file)
@@ -23,13 +23,14 @@ import io.swagger.v3.oas.annotations.headers.Header;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.OAuthScope;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.redback.authorization.RedbackAuthorization;
-import org.apache.archiva.rest.api.model.v2.Artifact;
 import org.apache.archiva.rest.api.model.v2.FileInfo;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepository;
+import org.apache.archiva.rest.api.model.v2.MavenManagedRepositoryUpdate;
 import org.apache.archiva.security.common.ArchivaRoleConstants;
 
 import javax.ws.rs.Consumes;
@@ -199,14 +200,14 @@ public interface MavenManagedRepositoryService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
-    MavenManagedRepository updateManagedRepository( MavenManagedRepository managedRepository )
+    MavenManagedRepository updateManagedRepository( @PathParam( "id" ) String repositoryId,  MavenManagedRepositoryUpdate managedRepository )
         throws ArchivaRestServiceException;
 
 
-    @Path( "{id}/a/{filePath: .+}" )
+    @Path( "{id}/path/{filePath: .+}" )
     @GET
     @Produces( {MediaType.APPLICATION_JSON} )
-    @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
+    @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS, resource = "{id}")
     @Operation( summary = "Returns the status of a given file in the repository",
         security = {
             @SecurityRequirement(
@@ -229,18 +230,28 @@ public interface MavenManagedRepositoryService
 
 
     /**
-     * permissions are checked in impl
+     * Permissions are checked in impl
      * will copy an artifact from the source repository to the target repository
      */
-    @Path ("{srcId}/a/{path: .+}/copyto/{dstId}")
+    @Path ("{srcId}/path/{path: .+}/copyto/{dstId}")
     @POST
     @Produces({APPLICATION_JSON})
     @RedbackAuthorization (noPermission = true)
     @Operation( summary = "Copies a artifact from the source repository to the destination repository",
         security = {
             @SecurityRequirement(
-                name = ArchivaRoleConstants.OPERATION_RUN_INDEXER
+                name = ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS,
+                scopes = {
+                    "{srcId}"
+                }
+            ),
+            @SecurityRequirement(
+                name= ArchivaRoleConstants.OPERATION_REPOSITORY_UPLOAD,
+                scopes = {
+                    "{dstId}"
+                }
             )
+
         },
         responses = {
             @ApiResponse( responseCode = "200",
@@ -257,7 +268,7 @@ public interface MavenManagedRepositoryService
         throws ArchivaRestServiceException;
 
 
-    @Path ("{id}/a/{path: .+}")
+    @Path ("{id}/path/{path: .+}")
     @DELETE
     @Consumes ({ APPLICATION_JSON })
     @RedbackAuthorization (noPermission = true)
@@ -280,7 +291,7 @@ public interface MavenManagedRepositoryService
     Response deleteArtifact( @PathParam( "id" ) String repositoryId, @PathParam( "path" ) String path )
         throws ArchivaRestServiceException;
 
-    @Path ("{id}/c/{namespace}/{projectId}/{version}")
+    @Path ( "{id}/co/{group}/{project}/{version}" )
     @DELETE
     @Produces ({ MediaType.APPLICATION_JSON })
     @RedbackAuthorization (noPermission = true)
@@ -301,12 +312,12 @@ public interface MavenManagedRepositoryService
         }
     )
     Response removeProjectVersion( @PathParam ( "id" ) String repositoryId,
-                                       @PathParam ( "namespace" ) String namespace, @PathParam ( "projectId" ) String projectId,
-                                       @PathParam ( "version" ) String version )
+                                   @PathParam ( "group" ) String namespace, @PathParam ( "project" ) String projectId,
+                                   @PathParam ( "version" ) String version )
         throws org.apache.archiva.rest.api.services.ArchivaRestServiceException;
 
 
-    @Path ( "{id}/c/{namespace}/{projectId}" )
+    @Path ( "{id}/co/{group}/{project}" )
     @DELETE
     @Produces ({ MediaType.APPLICATION_JSON })
     @RedbackAuthorization (noPermission = true)
@@ -326,10 +337,10 @@ public interface MavenManagedRepositoryService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
-    Response deleteProject( @PathParam ("id") String repositoryId, @PathParam ( "namespace" ) String namespace, @PathParam ("projectId") String projectId )
+    Response deleteProject( @PathParam ("id") String repositoryId, @PathParam ( "group" ) String namespace, @PathParam ( "project" ) String projectId )
         throws org.apache.archiva.rest.api.services.ArchivaRestServiceException;
 
-    @Path ( "{id}/c/{namespace}" )
+    @Path ( "{id}/co/{namespace}" )
     @DELETE
     @Produces ({ MediaType.APPLICATION_JSON })
     @RedbackAuthorization (noPermission = true)
index 7fbfe16ce28bc0653b2ec44d99e7c441ec193937..6fd85d5cf47fe42662d956eed0f18e24e772d085 100644 (file)
@@ -17,27 +17,62 @@ package org.apache.archiva.rest.services.v2;
  * under the License.
  */
 
+import org.apache.archiva.admin.model.AuditInformation;
 import org.apache.archiva.admin.model.RepositoryAdminException;
 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.components.rest.util.QueryHelper;
+import org.apache.archiva.redback.authentication.AuthenticationResult;
+import org.apache.archiva.redback.authorization.AuthorizationException;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.system.DefaultSecuritySession;
+import org.apache.archiva.redback.system.SecuritySession;
+import org.apache.archiva.redback.system.SecuritySystem;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
 import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.content.ContentItem;
+import org.apache.archiva.repository.content.LayoutException;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
+import org.apache.archiva.repository.storage.fs.FsStorageUtil;
+import org.apache.archiva.repository.storage.util.StorageUtil;
 import org.apache.archiva.rest.api.model.v2.Artifact;
 import org.apache.archiva.rest.api.model.v2.FileInfo;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepository;
+import org.apache.archiva.rest.api.model.v2.MavenManagedRepositoryUpdate;
 import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
 import org.apache.archiva.rest.api.services.v2.ErrorKeys;
 import org.apache.archiva.rest.api.services.v2.ErrorMessage;
 import org.apache.archiva.rest.api.services.v2.MavenManagedRepositoryService;
+import org.apache.archiva.security.common.ArchivaRoleConstants;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.inject.Inject;
+import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_REPOSITORY_ACCESS;
+import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_REPOSITORY_UPLOAD;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -45,81 +80,196 @@ import java.util.List;
 @Service("v2.managedMavenRepositoryService#rest")
 public class DefaultMavenManagedRepositoryService implements MavenManagedRepositoryService
 {
+    @Context
+    HttpServletResponse httpServletResponse;
+
+    @Context
+    UriInfo uriInfo;
+
     private static final Logger log = LoggerFactory.getLogger( DefaultMavenManagedRepositoryService.class );
-    private static final QueryHelper<org.apache.archiva.admin.model.beans.ManagedRepository> QUERY_HELPER = new QueryHelper<>( new String[]{"id", "name"} );
+    private static final QueryHelper<ManagedRepository> QUERY_HELPER = new QueryHelper<>( new String[]{"id", "name"} );
     static
     {
-        QUERY_HELPER.addStringFilter( "id", org.apache.archiva.admin.model.beans.ManagedRepository::getId );
-        QUERY_HELPER.addStringFilter( "name", org.apache.archiva.admin.model.beans.ManagedRepository::getName );
-        QUERY_HELPER.addStringFilter( "location", org.apache.archiva.admin.model.beans.ManagedRepository::getName );
-        QUERY_HELPER.addBooleanFilter( "snapshot", org.apache.archiva.admin.model.beans.ManagedRepository::isSnapshots );
-        QUERY_HELPER.addBooleanFilter( "release", org.apache.archiva.admin.model.beans.ManagedRepository::isReleases );
-        QUERY_HELPER.addNullsafeFieldComparator( "id", org.apache.archiva.admin.model.beans.ManagedRepository::getId );
-        QUERY_HELPER.addNullsafeFieldComparator( "name", org.apache.archiva.admin.model.beans.ManagedRepository::getName );
+        QUERY_HELPER.addStringFilter( "id", ManagedRepository::getId );
+        QUERY_HELPER.addStringFilter( "name", ManagedRepository::getName );
+        QUERY_HELPER.addStringFilter( "location", (r)  -> r.getLocation().toString() );
+        QUERY_HELPER.addBooleanFilter( "snapshot", (r) -> r.getActiveReleaseSchemes( ).contains( ReleaseScheme.SNAPSHOT ) );
+        QUERY_HELPER.addBooleanFilter( "release", (r) -> r.getActiveReleaseSchemes().contains( ReleaseScheme.RELEASE ));
+        QUERY_HELPER.addNullsafeFieldComparator( "id", ManagedRepository::getId );
+        QUERY_HELPER.addNullsafeFieldComparator( "name", ManagedRepository::getName );
     }
 
     private ManagedRepositoryAdmin managedRepositoryAdmin;
     private RepositoryRegistry repositoryRegistry;
+    private SecuritySystem securitySystem;
 
-    public DefaultMavenManagedRepositoryService( RepositoryRegistry repositoryRegistry, ManagedRepositoryAdmin managedRepositoryAdmin )
+    public DefaultMavenManagedRepositoryService( SecuritySystem securitySystem, RepositoryRegistry repositoryRegistry, ManagedRepositoryAdmin managedRepositoryAdmin )
     {
+        this.securitySystem = securitySystem;
+        this.repositoryRegistry = repositoryRegistry;
         this.managedRepositoryAdmin = managedRepositoryAdmin;
     }
 
+    protected AuditInformation getAuditInformation( )
+    {
+        RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get( );
+        User user = redbackRequestInformation == null ? null : redbackRequestInformation.getUser( );
+        String remoteAddr = redbackRequestInformation == null ? null : redbackRequestInformation.getRemoteAddr( );
+        return new AuditInformation( user, remoteAddr );
+    }
+
     @Override
-    public PagedResult<MavenManagedRepository> getManagedRepositories( String searchTerm, Integer offset, Integer limit, List<String> orderBy, String order ) throws ArchivaRestServiceException
+    public PagedResult<MavenManagedRepository> getManagedRepositories( final String searchTerm, final Integer offset,
+                                                                       final Integer limit, final List<String> orderBy,
+                                                                       final String order ) throws ArchivaRestServiceException
     {
         try
         {
-            List<org.apache.archiva.admin.model.beans.ManagedRepository> result = managedRepositoryAdmin.getManagedRepositories( );
-            int totalCount = Math.toIntExact( result.stream( ).count( ) );
+            Collection<ManagedRepository> repos = repositoryRegistry.getManagedRepositories( );
+            final Predicate<ManagedRepository> queryFilter = QUERY_HELPER.getQueryFilter( searchTerm ).and( r -> r.getType() == RepositoryType.MAVEN );
+            final Comparator<ManagedRepository> comparator = QUERY_HELPER.getComparator( orderBy, order );
+            int totalCount = Math.toIntExact( repos.stream( ).filter( queryFilter ).count( ) );
+            return PagedResult.of( totalCount, offset, limit, repos.stream( ).filter( queryFilter ).sorted( comparator )
+                .map(mr -> MavenManagedRepository.of(mr)).skip( offset ).limit( limit ).collect( Collectors.toList( ) ) );
         }
         catch (ArithmeticException e) {
             log.error( "Invalid number of repositories detected." );
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.INVALID_RESULT_SET_ERROR ) );
         }
-        catch ( RepositoryAdminException e )
-        {
-            e.printStackTrace( );
-        }
-        return null;
-
     }
 
     @Override
     public MavenManagedRepository getManagedRepository( String repositoryId ) throws ArchivaRestServiceException
     {
-        return null;
+        ManagedRepository repo = repositoryRegistry.getManagedRepository( repositoryId );
+        if (repo==null) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, repositoryId ), 404 );
+        }
+        if (repo.getType()!=RepositoryType.MAVEN) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_WRONG_TYPE, repositoryId, repo.getType().name() ), 404 );
+        }
+        return MavenManagedRepository.of( repo );
     }
 
     @Override
     public Response deleteManagedRepository( String repositoryId, boolean deleteContent ) throws ArchivaRestServiceException
     {
-        return null;
+        ManagedRepository repo = repositoryRegistry.getManagedRepository( repositoryId );
+        if (repo==null) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, repositoryId ), 404 );
+        }
+        if (repo.getType()!=RepositoryType.MAVEN) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_WRONG_TYPE, repositoryId, repo.getType().name() ), 404 );
+        }
+        try
+        {
+            managedRepositoryAdmin.deleteManagedRepository( repositoryId, getAuditInformation( ), deleteContent );
+            return Response.ok( ).build( );
+        }
+        catch ( RepositoryAdminException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_DELETE_FAILED, e.getMessage( ) ) );
+        }
+    }
+
+    private org.apache.archiva.admin.model.beans.ManagedRepository convert(MavenManagedRepository repository) {
+        org.apache.archiva.admin.model.beans.ManagedRepository repoBean = new org.apache.archiva.admin.model.beans.ManagedRepository( );
+        repoBean.setId( repository.getId( ) );
+        repoBean.setName( repository.getName() );
+        repoBean.setDescription( repository.getDescription() );
+        repoBean.setBlockRedeployments( repository.isBlocksRedeployments() );
+        repoBean.setCronExpression( repository.getSchedulingDefinition() );
+        repoBean.setLocation( repository.getLocation() );
+        repoBean.setReleases( repository.getReleaseSchemes().contains( ReleaseScheme.RELEASE.name() ) );
+        repoBean.setSnapshots( repository.getReleaseSchemes().contains( ReleaseScheme.SNAPSHOT.name() ) );
+        repoBean.setScanned( repository.isScanned() );
+        repoBean.setDeleteReleasedSnapshots( repository.isDeleteSnapshotsOfRelease() );
+        repoBean.setSkipPackedIndexCreation( repository.isSkipPackedIndexCreation() );
+        repoBean.setRetentionCount( repository.getRetentionCount() );
+        repoBean.setRetentionPeriod( repository.getRetentionPeriod().getDays() );
+        repoBean.setIndexDirectory( repository.getIndexPath() );
+        repoBean.setPackedIndexDirectory( repository.getPackedIndexPath() );
+        repoBean.setLayout( repository.getLayout() );
+        repoBean.setType( RepositoryType.MAVEN.name( ) );
+        return repoBean;
     }
 
     @Override
     public MavenManagedRepository addManagedRepository( MavenManagedRepository managedRepository ) throws ArchivaRestServiceException
     {
-        return null;
+        final String repoId = managedRepository.getId( );
+        if ( StringUtils.isEmpty( repoId ) ) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_INVALID_ID, repoId ), 422 );
+        }
+        Repository repo = repositoryRegistry.getRepository( repoId );
+        if (repo!=null) {
+            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder( ).path( repoId ).build( ).toString( ) );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ID_EXISTS, repoId ), 303 );
+        }
+        try
+        {
+            managedRepositoryAdmin.addManagedRepository( convert( managedRepository ), managedRepository.isHasStagingRepository(), getAuditInformation() );
+            httpServletResponse.setStatus( 201 );
+            return MavenManagedRepository.of( repositoryRegistry.getManagedRepository( repoId ) );
+        }
+        catch ( RepositoryAdminException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
+        }
     }
 
     @Override
-    public MavenManagedRepository updateManagedRepository( MavenManagedRepository managedRepository ) throws ArchivaRestServiceException
+    public MavenManagedRepository updateManagedRepository( final String repositoryId, final MavenManagedRepositoryUpdate managedRepository ) throws ArchivaRestServiceException
     {
-        return null;
+        org.apache.archiva.admin.model.beans.ManagedRepository repo = convert( managedRepository );
+        try
+        {
+            managedRepositoryAdmin.updateManagedRepository( repo, managedRepository.isHasStagingRepository( ), getAuditInformation( ), managedRepository.isResetStats( ) );
+            ManagedRepository newRepo = repositoryRegistry.getManagedRepository( managedRepository.getId( ) );
+            if (newRepo==null) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_UPDATE_FAILED, repositoryId ) );
+            }
+            return MavenManagedRepository.of( newRepo );
+        }
+        catch ( RepositoryAdminException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
+        }
     }
 
     @Override
     public FileInfo getFileStatus( String repositoryId, String fileLocation ) throws ArchivaRestServiceException
     {
-        return null;
+        ManagedRepository repo = repositoryRegistry.getManagedRepository( repositoryId );
+        if (repo==null) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, repositoryId ), 404 );
+        }
+        try
+        {
+            ContentItem contentItem = repo.getContent( ).toItem( fileLocation );
+            if (contentItem.getAsset( ).exists( ))  {
+                return FileInfo.of( contentItem.getAsset( ) );
+            } else {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.ARTIFACT_NOT_FOUND, repositoryId, fileLocation ), 404 );
+            }
+        }
+        catch ( LayoutException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_LAYOUT_ERROR, e.getMessage( ) ) );
+        }
     }
 
     @Override
     public Response copyArtifact( String srcRepositoryId, String dstRepositoryId,
                                   String path ) throws ArchivaRestServiceException
     {
+        final AuditInformation auditInformation = getAuditInformation( );
+        final String userName = auditInformation.getUser( ).getUsername( );
+        if ( StringUtils.isEmpty( userName ) )
+        {
+            httpServletResponse.setHeader( "WWW-Authenticate", "Bearer realm=\"archiva\"" );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.NOT_AUTHENTICATED ), 401 );
+        }
         ManagedRepository srcRepo = repositoryRegistry.getManagedRepository( srcRepositoryId );
         if (srcRepo==null) {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, srcRepositoryId ), 404 );
@@ -128,17 +278,89 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
         if (dstRepo==null) {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, dstRepositoryId ), 404 );
         }
-        if (dstRepo.getAsset( path ).exists()) {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.ARTIFACT_EXISTS_AT_DEST, path ) );
+        checkAuthority( auditInformation.getUser().getUsername(), srcRepositoryId, dstRepositoryId );
+        try
+        {
+            ContentItem srcItem = srcRepo.getContent( ).toItem( path );
+            ContentItem dstItem = dstRepo.getContent( ).toItem( path );
+            if (!srcItem.getAsset().exists()){
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.ARTIFACT_NOT_FOUND, srcRepositoryId, path ), 404 );
+            }
+            if (dstItem.getAsset().exists()) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.ARTIFACT_EXISTS_AT_DEST, srcRepositoryId, path ), 400 );
+            }
+            FsStorageUtil.copyAsset( srcItem.getAsset( ), dstItem.getAsset( ), true );
+        }
+        catch ( LayoutException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_LAYOUT_ERROR, e.getMessage() ) );
+        }
+        catch ( IOException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.ARTIFACT_COPY_ERROR, e.getMessage() ) );
+        }
+        return Response.ok( ).build();
+    }
+
+    private void checkAuthority(final String userName, final String srcRepositoryId, final String dstRepositoryId ) throws ArchivaRestServiceException {
+        User user = null;
+        try
+        {
+            user = securitySystem.getUserManager().findUser( userName );
+        }
+        catch ( UserNotFoundException e )
+        {
+            httpServletResponse.setHeader( "WWW-Authenticate", "Bearer realm=\"archiva\"" );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.USER_NOT_FOUND, userName ), 401 );
+        }
+        catch ( UserManagerException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.USER_MANAGER_ERROR, e.getMessage( ) ) );
+        }
+
+        // check karma on source : read
+        AuthenticationResult authn = new AuthenticationResult( true, userName, null );
+        SecuritySession securitySession = new DefaultSecuritySession( authn, user );
+        try
+        {
+            boolean authz =
+                securitySystem.isAuthorized( securitySession, OPERATION_REPOSITORY_ACCESS,
+                    srcRepositoryId );
+            if ( !authz )
+            {
+                throw new ArchivaRestServiceException(ErrorMessage.of( ErrorKeys.PERMISSION_REPOSITORY_DENIED, srcRepositoryId, OPERATION_REPOSITORY_ACCESS ), 403);
+            }
+        }
+        catch ( AuthorizationException e )
+        {
+            log.error( "Error reading permission: {}", e.getMessage(), e );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.AUTHORIZATION_ERROR, e.getMessage() ), 403);
+        }
+
+        // check karma on target: write
+        try
+        {
+            boolean authz =
+                securitySystem.isAuthorized( securitySession, ArchivaRoleConstants.OPERATION_REPOSITORY_UPLOAD,
+                    dstRepositoryId );
+            if ( !authz )
+            {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.PERMISSION_REPOSITORY_DENIED, dstRepositoryId, OPERATION_REPOSITORY_UPLOAD ) );
+            }
+        }
+        catch ( AuthorizationException e )
+        {
+            log.error( "Error reading permission: {}", e.getMessage(), e );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.AUTHORIZATION_ERROR, e.getMessage() ), 403);
         }
 
 
-        return null;
     }
 
     @Override
     public Response deleteArtifact( String repositoryId, String path ) throws ArchivaRestServiceException
     {
+
         return null;
     }
 
index f09ba013c4ba567c9c8b8c3cbb341622684027a6..6df8624f117b69924cc26531ed1001ad99693dcb 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.archiva.repository.RepositoryRegistry;
 import org.apache.archiva.repository.storage.fs.FilesystemAsset;
 import org.apache.archiva.metadata.audit.AuditListener;
 import org.apache.archiva.repository.maven.MavenManagedRepository;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.apache.archiva.webdav.util.MimeTypes;
 import org.apache.commons.lang3.StringUtils;
@@ -127,7 +128,7 @@ public class DavResourceTest
 
     private DavResource getDavResource( String logicalPath, Path file ) throws LayoutException
     {
-        return new ArchivaDavResource( new FilesystemAsset( repository, logicalPath, file.toAbsolutePath()) , logicalPath, repository, session, resourceLocator,
+        return new ArchivaDavResource( new FilesystemAsset( (FilesystemStorage) repository.getRoot().getStorage(), logicalPath, file.toAbsolutePath()) , logicalPath, repository, session, resourceLocator,
                                        resourceFactory, mimeTypes, Collections.<AuditListener> emptyList(), null);
     }
 
@@ -349,7 +350,7 @@ public class DavResourceTest
         {
             try
             {
-                return new ArchivaDavResource( new FilesystemAsset(repository, "/" , baseDir.toAbsolutePath()), "/", repository, session, resourceLocator,
+                return new ArchivaDavResource( new FilesystemAsset( (FilesystemStorage) repository.getRoot().getStorage(), "/" , baseDir.toAbsolutePath()), "/", repository, session, resourceLocator,
                                                resourceFactory, mimeTypes, Collections.<AuditListener> emptyList(),
                                                null );
             }