]> source.dussan.org Git - archiva.git/commitdiff
[MRM-159] should not respond with a 404 if proxying a file results in a remote error
authorBrett Porter <brett@apache.org>
Thu, 3 Apr 2008 12:03:54 +0000 (12:03 +0000)
committerBrett Porter <brett@apache.org>
Thu, 3 Apr 2008 12:03:54 +0000 (12:03 +0000)
Merged from: r644205, 644275

git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@644276 13f79535-47bb-0310-9956-ffa450edef68

25 files changed:
archiva-docs/src/site/apt/adminguide/proxy-connectors.apt
archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java
archiva-modules/archiva-base/archiva-configuration/src/main/mdo/configuration.mdo
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/ChecksumPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/DownloadErrorPolicy.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/DownloadPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/Policy.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PostDownloadPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PreDownloadPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsDownloadPolicy.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsOnUpdateDownloadPolicy.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/ProxyDownloadException.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/ReleasesPolicy.java
archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/SnapshotsPolicy.java
archiva-modules/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java
archiva-modules/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/RepositoryProxyConnectors.java
archiva-modules/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java
archiva-modules/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/ErrorHandlingTest.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ErrorHandlingTest.xml [new file with mode: 0644]
archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/connectors/proxy/AbstractProxyConnectorFormAction.java
archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/include/proxyConnectorForm.jspf
archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.java
archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.java

index cf4dd72a3af92812629bf382792bb39d0d0e52e9..5737091be52b415620c5ec963f2f55477d4bf952 100644 (file)
@@ -29,14 +29,18 @@ Understanding Proxy Connector Configuration of Apache Archiva
 
   By default, Archiva comes with the following policies:
 
-    * <<<releases>>> - how to behave for released artifact metadata (those not carrying a <<<SNAPSHOT>>> version). This can be set to <<<always>>> (default), <<<hourly>>>, <<<daily>>>, <<<once>>> and <<<never>>>.
+    * <<<Releases>>> - how to behave for released artifact metadata (those not carrying a <<<SNAPSHOT>>> version). This can be set to <<<always>>> (default), <<<hourly>>>, <<<daily>>>, <<<once>>> and <<<never>>>.
 
-    * <<<snapshots>>> - how to behave for snapshot artifact metadata (those carrying a <<<SNAPSHOT>>> version). This can be set to <<<always>>> (default), <<<hourly>>>, <<<daily>>>, <<<once>>> and <<<never>>>.
+    * <<<Snapshots>>> - how to behave for snapshot artifact metadata (those carrying a <<<SNAPSHOT>>> version). This can be set to <<<always>>> (default), <<<hourly>>>, <<<daily>>>, <<<once>>> and <<<never>>>.
 
-    * <<<checksum>>> - how to handle incorrect checksums when downloading an artifact from the remote repository (ie, the checksum of the artifact does not match the corresponding detached checksum file).
+    * <<<Checksum>>> - how to handle incorrect checksums when downloading an artifact from the remote repository (ie, the checksum of the artifact does not match the corresponding detached checksum file).
       The options are to fail the request for the remote artifact, fix the checksum on the fly (default), or simply ignore the incorrect checksum
 
-    * <<<cache-failures>>> - whether failures retrieving the remote artifact should be cached (to save network bandwidth for missing or bad artifacts), or uncached (default).
+    * <<<Cache failures>>> - whether failures retrieving the remote artifact should be cached (to save network bandwidth for missing or bad artifacts), or uncached (default).
+
+    * <<<Return error when>>> - if a remote proxy causes an error, this option determines whether an existing artifact should be returned (error when <<<artifact not already present>>>), or the error passed on regardless (<<<always>>>).
+
+    * <<<On remote error>>> - if a remote error is encountered, <<<stop>>> causes the error to be returned immediately, <<<queue error>>> will return all errors after checking for other successful remote repositories first, and <<<ignore>>> will disregard ay errors.
 
     []
 
index ea0f57ac0e73da9ad8ff20e0cdff4b3bb8f94e03..cc863653198a7aad0aaa860f7885a7a0f99d81b9 100644 (file)
@@ -29,7 +29,7 @@ import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryW
 import org.apache.maven.archiva.policies.AbstractUpdatePolicy;
 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
 import org.apache.maven.archiva.policies.ChecksumPolicy;
-import org.apache.maven.archiva.policies.DownloadPolicy;
+import org.apache.maven.archiva.policies.Policy;
 import org.apache.maven.archiva.policies.PostDownloadPolicy;
 import org.apache.maven.archiva.policies.PreDownloadPolicy;
 import org.codehaus.plexus.evaluator.DefaultExpressionEvaluator;
@@ -280,7 +280,7 @@ public class DefaultArchivaConfiguration
                     // Validate existance of policy key.
                     if ( policyExists( policyId ) )
                     {
-                        DownloadPolicy policy = findPolicy( policyId );
+                        Policy policy = findPolicy( policyId );
                         // Does option exist?
                         if ( !policy.getOptions().contains( setting ) )
                         {
@@ -323,7 +323,7 @@ public class DefaultArchivaConfiguration
         return config;
     }
 
-    private DownloadPolicy findPolicy( String policyId )
+    private Policy findPolicy( String policyId )
     {
         if ( MapUtils.isEmpty( prePolicies ) )
         {
@@ -337,7 +337,7 @@ public class DefaultArchivaConfiguration
             return null;
         }
 
-        DownloadPolicy policy;
+        Policy policy;
 
         policy = prePolicies.get( policyId );
         if ( policy != null )
index babc602a3a8ead9e3ef18b847d84c1f693739fe4..77367438406fde41c28ca8fe580bad4a6bd5a94c 100644 (file)
      */
     public static final int UNORDERED = 0;
 
+    /**
+     * The policy key {@link #getPolicies()} for error handling.
+     * See {@link org.apache.maven.archiva.policies.DownloadErrorPolicy}
+     * for details on potential values to this policy key.
+     */
+    public static final String POLICY_PROPAGATE_ERRORS = "propagate-errors";
+
+    /**
+     * The policy key {@link #getPolicies()} for error handling when an artifact is present.
+     * See {@link org.apache.maven.archiva.policies.DownloadErrorPolicy}
+     * for details on potential values to this policy key.
+     */
+    public static final String POLICY_PROPAGATE_ERRORS_ON_UPDATE = "propagate-errors-on-update";
+
     /**
      * The policy key {@link #getPolicies()} for snapshot handling.
      * See {@link org.apache.maven.archiva.policies.SnapshotsPolicy}
index 77bd3b0ba569f2c6a0a740bc2c3d444df1b405cc..a69227ce90cb61dacf6fffce1fbe0e4ccb0716e7 100644 (file)
@@ -108,6 +108,11 @@ public class CachedFailuresPolicy
         return "cache-failures";
     }
 
+    public String getName()
+    {
+        return "Cache failures";
+    }
+
     public List<String> getOptions()
     {
         return options;
index 9b91c42d116be1781952d67947a799dbbffa2caa..99e741e0c6f156af6852848f8ecb6d3f2e045aeb 100644 (file)
@@ -157,6 +157,11 @@ public class ChecksumPolicy
         return "checksum";
     }
 
+    public String getName()
+    {
+        return "Checksum";
+    }
+
     public List<String> getOptions()
     {
         return options;
diff --git a/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/DownloadErrorPolicy.java b/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/DownloadErrorPolicy.java
new file mode 100644 (file)
index 0000000..b5c209c
--- /dev/null
@@ -0,0 +1,50 @@
+package org.apache.maven.archiva.policies;
+
+/*
+ * 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 java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Policy to apply after the download has completed, but before the
+ * resource is made available to the calling client.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ * @version $Id$
+ */
+public interface DownloadErrorPolicy
+    extends Policy
+{
+    /**
+     * Apply the download error policy.
+     *
+     * @param policySetting      the policy setting.
+     * @param request            the list of request properties that the policy might use.
+     * @param localFile
+     * @param exception          the exception that triggered the error
+     * @param previousExceptions any previously triggered exceptions
+     * @return whether to process the exception or not
+     * @throws PolicyConfigurationException if the policy is improperly configured
+     */
+    public boolean applyPolicy( String policySetting, Properties request, File localFile, Exception exception,
+                                Map<String, Exception> previousExceptions )
+        throws PolicyConfigurationException;
+}
\ No newline at end of file
index 6d506308da67f544438ea5f6139e2aabcae98f3e..68036d9e8c7c96c5cfa81318bdc857caded7e66a 100644 (file)
@@ -20,7 +20,6 @@ package org.apache.maven.archiva.policies;
  */
 
 import java.io.File;
-import java.util.List;
 import java.util.Properties;
 
 /**
@@ -30,27 +29,8 @@ import java.util.Properties;
  * @version $Id$
  */
 public interface DownloadPolicy
+    extends Policy
 {
-    /**
-     * Get the list of options for this policy.
-     * 
-     * @return the list of options for this policy.
-     */
-    public List<String> getOptions();
-
-    /**
-     * Get the default option for this policy.
-     * 
-     * @return the default policy for this policy.
-     */
-    public String getDefaultOption();
-
-    /**
-     * Get the id for this policy.
-     * 
-     * @return the id for this policy.
-     */
-    public String getId();
 
     /**
      * Apply the download policy.
diff --git a/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/Policy.java b/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/Policy.java
new file mode 100644 (file)
index 0000000..f7add1b
--- /dev/null
@@ -0,0 +1,55 @@
+package org.apache.maven.archiva.policies;
+
+/*
+ * 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 java.util.List;
+
+public interface Policy
+{
+    /**
+     * Get the list of options for this policy.
+     *
+     * @return the list of options for this policy.
+     */
+    List<String> getOptions();
+
+    /**
+     * Get the default option for this policy.
+     *
+     * @return the default policy for this policy.
+     */
+    String getDefaultOption();
+
+    /**
+     * Get the id for this policy.
+     *
+     * @return the id for this policy.
+     */
+    String getId();
+
+    /**
+     * Get the display name for this policy.
+     *
+     * @todo i18n
+     *
+     * @return the name for this policy
+     */
+    String getName();
+}
index d1f6d216e652a42d9ffef0e7a11f0b2a9a50a1be..12bb6b41670e26248485b8f3b6539cadf2ee56ce 100644 (file)
@@ -19,9 +19,6 @@ package org.apache.maven.archiva.policies;
  * under the License.
  */
 
-import java.io.File;
-import java.util.Properties;
-
 /**
  * Policy to apply after the download has completed, but before the
  * resource is made available to the calling client. 
@@ -32,18 +29,4 @@ import java.util.Properties;
 public interface PostDownloadPolicy
     extends DownloadPolicy
 {
-    /**
-     * Apply the download policy.
-     * 
-     * A true result allows the download to succeed.  false indicates that the 
-     * download is a failure.
-     * 
-     * @param policySetting the policy setting.
-     * @param request the list of request properties that the policy might use.
-     * @param localFile the local file that this policy affects
-     * 
-     * @throws PolicyViolationException if the policy has been violated.
-     */
-    public void applyPolicy( String policySetting, Properties request, File localFile )
-        throws PolicyViolationException, PolicyConfigurationException;
 }
index f88eb938f065ef811a72b7f748608d148da2cb3d..1faf52025b223891ad8514677bad7573d697352f 100644 (file)
@@ -20,9 +20,6 @@ package org.apache.maven.archiva.policies;
  */
 
 
-import java.io.File;
-import java.util.Properties;
-
 /**
  * Policy to apply before the download is attempted.
  *
@@ -31,18 +28,4 @@ import java.util.Properties;
  */
 public interface PreDownloadPolicy extends DownloadPolicy
 {
-    /**
-     * Apply the download policy.
-     * 
-     * A true result lets the download occur.  A false result prevents the download
-     * from occuring. 
-     * 
-     * @param policySetting the policy setting.
-     * @param request the list of request properties that the policy might use.
-     * @param localFile the local file that this policy affects
-     * 
-     * @throws PolicyViolationException if the policy has been violated.
-     */
-    public void applyPolicy( String policySetting, Properties request, File localFile )
-        throws PolicyViolationException, PolicyConfigurationException;
 }
diff --git a/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsDownloadPolicy.java b/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsDownloadPolicy.java
new file mode 100644 (file)
index 0000000..cca1c67
--- /dev/null
@@ -0,0 +1,118 @@
+package org.apache.maven.archiva.policies;
+
+/*
+ * 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.commons.lang.StringUtils;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * PropagateErrorsPolicy - a policy applied on error to determine how to treat the error.
+ *
+ * @plexus.component role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
+ * role-hint="propagate-errors"
+ */
+public class PropagateErrorsDownloadPolicy
+    extends AbstractLogEnabled
+    implements DownloadErrorPolicy
+{
+    /**
+     * Signifies any error should stop searching for other proxies.
+     */
+    public static final String STOP = "stop";
+
+    /**
+     * Propagate errors at the end after all are gathered, if there was no successful download from other proxies.
+     */
+    public static final String QUEUE = "queue error";
+
+    /**
+     * Ignore errors and treat as if it were not found.
+     */
+    public static final String IGNORE = "ignore";
+
+    private List<String> options = new ArrayList<String>();
+
+    public PropagateErrorsDownloadPolicy()
+    {
+        options.add( STOP );
+        options.add( QUEUE );
+        options.add( IGNORE );
+    }
+
+    public boolean applyPolicy( String policySetting, Properties request, File localFile, Exception exception,
+                                Map<String, Exception> previousExceptions )
+        throws PolicyConfigurationException
+    {
+        if ( !options.contains( policySetting ) )
+        {
+            // Not a valid code.
+            throw new PolicyConfigurationException( "Unknown error policy setting [" + policySetting +
+                "], valid settings are [" + StringUtils.join( options.iterator(), "," ) + "]" );
+        }
+
+        if ( IGNORE.equals( policySetting ) )
+        {
+            // Ignore.
+            getLogger().debug( "Error policy set to IGNORE." );
+            return false;
+        }
+
+        String repositoryId = request.getProperty( "remoteRepositoryId" );
+        if ( STOP.equals( policySetting ) )
+        {
+            return true;
+        }
+
+        if ( QUEUE.equals( policySetting ) )
+        {
+            previousExceptions.put( repositoryId, exception );
+            return true;
+        }
+
+        throw new PolicyConfigurationException(
+            "Unable to process checksum policy of [" + policySetting + "], please file a bug report." );
+    }
+
+    public String getDefaultOption()
+    {
+        return QUEUE;
+    }
+
+    public String getId()
+    {
+        return "propagate-errors";
+    }
+
+    public String getName()
+    {
+        return "On remote error";
+    }
+
+    public List<String> getOptions()
+    {
+        return options;
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsOnUpdateDownloadPolicy.java b/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/PropagateErrorsOnUpdateDownloadPolicy.java
new file mode 100644 (file)
index 0000000..d2b471a
--- /dev/null
@@ -0,0 +1,105 @@
+package org.apache.maven.archiva.policies;
+
+/*
+ * 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.commons.lang.StringUtils;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * PropagateErrorsPolicy - a policy applied on error to determine how to treat the error.
+ *
+ * @plexus.component role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
+ *                   role-hint="propagate-errors-on-update"
+ */
+public class PropagateErrorsOnUpdateDownloadPolicy
+    extends AbstractLogEnabled
+    implements DownloadErrorPolicy
+{
+    /**
+     * Signifies any error should cause a failure whether the artifact is already present or not.
+     */
+    public static final String ALWAYS = "always";
+
+    /**
+     * Signifies any error should cause a failure only if the artifact is not already present.
+     */
+    public static final String NOT_PRESENT = "artifact not already present";
+
+    private List<String> options = new ArrayList<String>();
+
+    public PropagateErrorsOnUpdateDownloadPolicy()
+    {
+        options.add( ALWAYS );
+        options.add( NOT_PRESENT );
+    }
+
+    public boolean applyPolicy( String policySetting, Properties request, File localFile, Exception exception,
+                             Map<String,Exception> previousExceptions )
+        throws PolicyConfigurationException
+    {
+        if ( !options.contains( policySetting ) )
+        {
+            // Not a valid code.
+            throw new PolicyConfigurationException( "Unknown error policy setting [" + policySetting
+                + "], valid settings are [" + StringUtils.join( options.iterator(), "," ) + "]" );
+        }
+
+        if ( ALWAYS.equals( policySetting ) )
+        {
+            // throw ther exception regardless
+            return true;
+        }
+
+        if ( NOT_PRESENT.equals( policySetting ) )
+        {
+            // cancel the exception if the file exists
+            return !localFile.exists();
+        }
+
+        throw new PolicyConfigurationException( "Unable to process checksum policy of [" + policySetting
+            + "], please file a bug report." );
+    }
+
+    public String getDefaultOption()
+    {
+        return NOT_PRESENT;
+    }
+
+    public String getId()
+    {
+        return "propagate-errors-on-update";
+    }
+
+    public String getName()
+    {
+        return "Return error when";
+    }
+
+    public List<String> getOptions()
+    {
+        return options;
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/ProxyDownloadException.java b/archiva-modules/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/ProxyDownloadException.java
new file mode 100644 (file)
index 0000000..9d328c6
--- /dev/null
@@ -0,0 +1,66 @@
+package org.apache.maven.archiva.policies;
+
+/*
+ * 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.maven.archiva.common.ArchivaException;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * One or more exceptions occurred downloading from a remote repository during the proxy phase.
+ */
+public class ProxyDownloadException
+    extends ArchivaException
+{
+    /**
+     * A list of failures keyed by repository ID.
+     */
+    private final Map<String, Exception> failures;
+
+    public ProxyDownloadException( String message, String repositoryId, Exception cause )
+    {
+        super( constructMessage( message, Collections.singletonMap( repositoryId, cause ) ), cause );
+
+        failures = Collections.singletonMap( repositoryId, cause );
+    }
+
+    public ProxyDownloadException( String message, Map<String, Exception> failures )
+    {
+        super( constructMessage( message, failures ) );
+
+        this.failures = failures;
+    }
+
+    private static String constructMessage( String message, Map<String, Exception> failures )
+    {
+        String msg = message + ":";
+        for ( Map.Entry<String, Exception> entry : failures.entrySet() )
+        {
+            msg += "\n\t" + entry.getKey() + ": " + entry.getValue().getMessage();
+        }
+        return msg;
+    }
+
+    public Map<String,Exception> getFailures()
+    {
+        return failures;
+    }
+}
index 8b53461a87242377793ca3d33772ddfdf56345fa..86ad7c3fca36d667809318b0e056c94576e9e8bc 100644 (file)
@@ -55,4 +55,9 @@ public class ReleasesPolicy
     {
         return "releases";
     }
+
+    public String getName()
+    {
+        return "Releases";
+    }
 }
index ffa903262f849b8487fcdc5f57660a7a4adacf23..ea10023c35ae5a66ab8ff2525d3b182dd3369d61 100644 (file)
@@ -55,4 +55,9 @@ public class SnapshotsPolicy
     {
         return "snapshots";
     }
+
+    public String getName()
+    {
+        return "Snapshots";
+    }
 }
index f0b867481d6300dc689782c68dccba2397b41752..1954f827dea29a9f5cabe6f879b44e75fbfb1c14 100644 (file)
@@ -19,16 +19,6 @@ package org.apache.maven.archiva.proxy;
  * under the License.
  */
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Map.Entry;
-
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
@@ -41,11 +31,13 @@ import org.apache.maven.archiva.model.Keys;
 import org.apache.maven.archiva.model.ProjectReference;
 import org.apache.maven.archiva.model.RepositoryURL;
 import org.apache.maven.archiva.model.VersionedReference;
+import org.apache.maven.archiva.policies.DownloadErrorPolicy;
 import org.apache.maven.archiva.policies.DownloadPolicy;
 import org.apache.maven.archiva.policies.PolicyConfigurationException;
 import org.apache.maven.archiva.policies.PolicyViolationException;
 import org.apache.maven.archiva.policies.PostDownloadPolicy;
 import org.apache.maven.archiva.policies.PreDownloadPolicy;
+import org.apache.maven.archiva.policies.ProxyDownloadException;
 import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
 import org.apache.maven.archiva.repository.ContentNotFoundException;
 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
@@ -73,11 +65,23 @@ import org.codehaus.plexus.util.SelectorUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
 /**
  * DefaultRepositoryProxyConnectors
  *
  * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
  * @version $Id$
+ * @todo exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than your average brown onion
  * @plexus.component role-hint="default"
  */
 public class DefaultRepositoryProxyConnectors
@@ -116,7 +120,12 @@ public class DefaultRepositoryProxyConnectors
     private Map<String, PostDownloadPolicy> postDownloadPolicies;
 
     /**
-     * @plexus.requirement
+     * @plexus.requirement role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
+     */
+    private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
+
+    /**
+     * @plexus.requirement role-hint="default"
      */
     private UrlFailureCache urlFailureCache;
 
@@ -136,26 +145,31 @@ public class DefaultRepositoryProxyConnectors
      * @param artifact   the artifact reference to fetch.
      * @return the local file in the managed repository that was fetched, or null if the artifact was not (or
      *         could not be) fetched.
-     * @throws ProxyException if there was a problem fetching the artifact.
+     * @throws PolicyViolationException if there was a problem fetching the artifact.
      */
     public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
+        throws ProxyDownloadException
     {
         File localFile = toLocalFile( repository, artifact );
 
         Properties requestProperties = new Properties();
         requestProperties.setProperty( "filetype", "artifact" );
         requestProperties.setProperty( "version", artifact.getVersion() );
+        requestProperties.setProperty( "managedRepositoryId", repository.getId() );
 
         List<ProxyConnector> connectors = getProxyConnectors( repository );
+        Map<String, Exception> previousExceptions = new LinkedHashMap<String, Exception>();
         for ( ProxyConnector connector : connectors )
         {
             RemoteRepositoryContent targetRepository = connector.getTargetRepository();
+            requestProperties.setProperty( "remoteRepositoryId", targetRepository.getId() );
+
             String targetPath = targetRepository.toPath( artifact );
 
             try
             {
-                File downloadedFile = transferFile( connector, targetRepository, targetPath, localFile,
-                                                    requestProperties );
+                File downloadedFile =
+                    transferFile( connector, targetRepository, targetPath, localFile, requestProperties );
 
                 if ( fileExists( downloadedFile ) )
                 {
@@ -175,12 +189,17 @@ public class DefaultRepositoryProxyConnectors
             }
             catch ( ProxyException e )
             {
-                log.warn( "Transfer error from repository \"" + targetRepository.getRepository().getId() +
-                    "\" for artifact " + Keys.toKey( artifact ) + ", continuing to next repository. Error message: " +
-                    e.getMessage() );
-                log.debug( "Full stack trace", e );
+                validatePolicies( this.downloadErrorPolicies, connector.getPolicies(), requestProperties, artifact,
+                                  targetRepository, localFile, e, previousExceptions );
             }
         }
+
+        if ( !previousExceptions.isEmpty() )
+        {
+            throw new ProxyDownloadException( "Failures occurred downloading from some remote repositories",
+                                              previousExceptions );
+        }
+
         log.debug( "Exhausted all target repositories, artifact " + Keys.toKey( artifact ) + " not found." );
 
         return null;
@@ -471,10 +490,10 @@ public class DefaultRepositoryProxyConnectors
      * @param localFile         the local file to place the downloaded resource into
      * @param requestProperties the request properties to utilize for policy handling.
      * @return the local file that was downloaded, or null if not downloaded.
-     * @throws NotFoundException if the file was not found on the remote repository.
+     * @throws NotFoundException    if the file was not found on the remote repository.
      * @throws NotModifiedException if the localFile was present, and the resource was present on remote repository,
      *                              but the remote resource is not newer than the local File.
-     * @throws ProxyException if transfer was unsuccessful.
+     * @throws ProxyException       if transfer was unsuccessful.
      */
     private File transferFile( ProxyConnector connector, RemoteRepositoryContent remoteRepository, String remotePath,
                                File localFile, Properties requestProperties )
@@ -696,8 +715,8 @@ public class DefaultRepositoryProxyConnectors
                 success = wagon.getIfNewer( remotePath, temp, localFile.lastModified() );
                 if ( !success )
                 {
-                    throw new NotModifiedException( "Not downloaded, as local file is newer than remote side: "
-                                                    + localFile.getAbsolutePath() );
+                    throw new NotModifiedException(
+                        "Not downloaded, as local file is newer than remote side: " + localFile.getAbsolutePath() );
                 }
 
                 if ( temp.exists() )
@@ -711,13 +730,15 @@ public class DefaultRepositoryProxyConnectors
         }
         catch ( ResourceDoesNotExistException e )
         {
-            throw new NotFoundException( "Resource [" + remoteRepository.getURL() + "/" + remotePath
-                + "] does not exist: " + e.getMessage(), e );
+            throw new NotFoundException(
+                "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
+                e );
         }
         catch ( WagonException e )
         {
-            throw new ProxyException( "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:"
-                                  + e.getMessage(), e );
+            throw new ProxyException(
+                "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage(),
+                e );
         }
         finally
         {
@@ -742,10 +763,10 @@ public class DefaultRepositoryProxyConnectors
     {
         for ( Entry<String, ? extends DownloadPolicy> entry : policies.entrySet() )
         {
-            String key = (String) entry.getKey();
+            String key = entry.getKey();
             DownloadPolicy policy = entry.getValue();
             String defaultSetting = policy.getDefaultOption();
-            String setting = StringUtils.defaultString( (String) settings.get( key ), defaultSetting );
+            String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
 
             log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
             try
@@ -759,6 +780,56 @@ public class DefaultRepositoryProxyConnectors
         }
     }
 
+    private void validatePolicies( Map<String, DownloadErrorPolicy> policies, Map<String, String> settings,
+                                   Properties request, ArtifactReference artifact, RemoteRepositoryContent content,
+                                   File localFile, ProxyException exception, Map<String, Exception> previousExceptions )
+        throws ProxyDownloadException
+    {
+        boolean process = true;
+        for ( Entry<String, ? extends DownloadErrorPolicy> entry : policies.entrySet() )
+        {
+            String key = entry.getKey();
+            DownloadErrorPolicy policy = entry.getValue();
+            String defaultSetting = policy.getDefaultOption();
+            String setting = StringUtils.defaultString( settings.get( key ), defaultSetting );
+
+            log.debug( "Applying [" + key + "] policy with [" + setting + "]" );
+            try
+            {
+                // all policies must approve the exception, any can cancel
+                process = policy.applyPolicy( setting, request, localFile, exception, previousExceptions );
+                if ( !process )
+                {
+                    break;
+                }
+            }
+            catch ( PolicyConfigurationException e )
+            {
+                log.error( e.getMessage(), e );
+            }
+        }
+
+        if ( process )
+        {
+            // if the exception was queued, don't throw it
+            if ( !previousExceptions.containsKey( content.getId() ) )
+            {
+                throw new ProxyDownloadException(
+                    "An error occurred in downloading from the remote repository, and the policy is to fail immediately",
+                    content.getId(), exception );
+            }
+        }
+        else
+        {
+            // if the exception was queued, but cancelled, remove it
+            previousExceptions.remove( content.getId() );
+        }
+
+        log.warn( "Transfer error from repository \"" + content.getRepository().getId() + "\" for artifact " +
+            Keys.toKey( artifact ) + ", continuing to next repository. Error message: " + exception.getMessage() );
+        log.debug( "Full stack trace", exception );
+    }
+
     /**
      * Used to move the temporary file to its real destination.  This is patterned from the way WagonManager handles
      * its downloaded files.
@@ -802,7 +873,8 @@ public class DefaultRepositoryProxyConnectors
      * @param remoteRepository the remote repository to connect to.
      * @return true if the connection was successful. false if not connected.
      */
-    private boolean connectToRepository( ProxyConnector connector, Wagon wagon, RemoteRepositoryContent remoteRepository )
+    private boolean connectToRepository( ProxyConnector connector, Wagon wagon,
+                                         RemoteRepositoryContent remoteRepository )
     {
         boolean connected = false;
 
@@ -851,15 +923,13 @@ public class DefaultRepositoryProxyConnectors
         catch ( ConnectionException e )
         {
             log.warn(
-                              "Could not connect to " + remoteRepository.getRepository().getName() + ": "
-                                  + e.getMessage() );
+                "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
             connected = false;
         }
         catch ( AuthenticationException e )
         {
             log.warn(
-                              "Could not connect to " + remoteRepository.getRepository().getName() + ": "
-                                  + e.getMessage() );
+                "Could not connect to " + remoteRepository.getRepository().getName() + ": " + e.getMessage() );
             connected = false;
         }
 
@@ -911,10 +981,10 @@ public class DefaultRepositoryProxyConnectors
 
     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
     {
-        if ( ConfigurationNames.isNetworkProxy( propertyName )
-            || ConfigurationNames.isManagedRepositories( propertyName )
-            || ConfigurationNames.isRemoteRepositories( propertyName )
-            || ConfigurationNames.isProxyConnector( propertyName ) )
+        if ( ConfigurationNames.isNetworkProxy( propertyName ) ||
+            ConfigurationNames.isManagedRepositories( propertyName ) ||
+            ConfigurationNames.isRemoteRepositories( propertyName ) ||
+            ConfigurationNames.isProxyConnector( propertyName ) )
         {
             initConnectorsAndNetworkProxies();
         }
index 6d832bfbbc268a004510d0644bcbae97acb2314c..c27c5b8ab01b1a3f943c2cea8f967c967b72a46c 100644 (file)
@@ -22,6 +22,7 @@ package org.apache.maven.archiva.proxy;
 import org.apache.maven.archiva.model.ArtifactReference;
 import org.apache.maven.archiva.model.ProjectReference;
 import org.apache.maven.archiva.model.VersionedReference;
+import org.apache.maven.archiva.policies.ProxyDownloadException;
 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
 
 import java.io.File;
@@ -45,11 +46,11 @@ public interface RepositoryProxyConnectors
      * @param repository the source repository to use. (must be a managed repository)
      * @param artifact the artifact to fetch.
      * @return true if the fetch operation succeeded in obtaining content, false if no content was obtained.
-     * @throws ProxyException if there was a problem fetching the content from the target repositories.
+     * @throws ProxyDownloadException if there was a problem fetching the content from the target repositories.
      */
     public File fetchFromProxies( ManagedRepositoryContent repository, ArtifactReference artifact )
-        throws ProxyException;
-    
+        throws ProxyDownloadException;
+
     /**
      * Performs the metadata fetch operation against the target repositories
      * of the provided source repository.
@@ -60,11 +61,9 @@ public interface RepositoryProxyConnectors
      * @param repository the source repository to use. (must be a managed repository)
      * @param metadata the metadata to fetch.
      * @return true if the fetch operation succeeded in obtaining content, false if no content was obtained.
-     * @throws ProxyException if there was a problem fetching the content from the target repositories.
      */
-    public File fetchFromProxies( ManagedRepositoryContent repository, VersionedReference metadata )
-        throws ProxyException;
-    
+    public File fetchFromProxies( ManagedRepositoryContent repository, VersionedReference metadata );
+
     /**
      * Performs the metadata fetch operation against the target repositories
      * of the provided source repository.
@@ -75,10 +74,8 @@ public interface RepositoryProxyConnectors
      * @param repository the source repository to use. (must be a managed repository)
      * @param metadata the metadata to fetch.
      * @return true if the fetch operation succeeded in obtaining content, false if no content was obtained.
-     * @throws ProxyException if there was a problem fetching the content from the target repositories.
      */
-    public File fetchFromProxies( ManagedRepositoryContent repository, ProjectReference metadata )
-        throws ProxyException;
+    public File fetchFromProxies( ManagedRepositoryContent repository, ProjectReference metadata );
 
     /**
      * Get the List of {@link ProxyConnector} objects of the source repository.
index c6a5f3e7fad1cf827eb773bfd8985b354a4ae8f6..88518a078e298d797796b6daf410cc1e269bfeea 100644 (file)
@@ -37,6 +37,8 @@ import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
 import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
 import org.apache.maven.archiva.policies.ChecksumPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsDownloadPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsOnUpdateDownloadPolicy;
 import org.apache.maven.archiva.policies.ReleasesPolicy;
 import org.apache.maven.archiva.policies.SnapshotsPolicy;
 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
@@ -295,8 +297,23 @@ public abstract class AbstractProxyTestCase
                        SnapshotsPolicy.ALWAYS, CachedFailuresPolicy.NO );
     }
 
-    protected void saveConnector( String sourceRepoId, String targetRepoId, String checksumPolicy,
-                                  String releasePolicy, String snapshotPolicy, String cacheFailuresPolicy )
+    protected void saveConnector( String sourceRepoId, String targetRepoId, String checksumPolicy, String releasePolicy,
+                                  String snapshotPolicy, String cacheFailuresPolicy )
+    {
+        saveConnector( sourceRepoId, targetRepoId, checksumPolicy, releasePolicy, snapshotPolicy, cacheFailuresPolicy,
+                       PropagateErrorsDownloadPolicy.QUEUE );
+    }
+
+    protected void saveConnector( String sourceRepoId, String targetRepoId, String checksumPolicy, String releasePolicy,
+                                  String snapshotPolicy, String cacheFailuresPolicy, String errorPolicy )
+    {
+        saveConnector( sourceRepoId, targetRepoId, checksumPolicy, releasePolicy, snapshotPolicy, cacheFailuresPolicy,
+                       errorPolicy, PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+    }
+
+    protected void saveConnector( String sourceRepoId, String targetRepoId, String checksumPolicy, String releasePolicy,
+                                  String snapshotPolicy, String cacheFailuresPolicy, String errorPolicy,
+                                  String errorOnUpdatePolicy )
     {
         ProxyConnectorConfiguration connectorConfig = new ProxyConnectorConfiguration();
         connectorConfig.setSourceRepoId( sourceRepoId );
@@ -305,6 +322,8 @@ public abstract class AbstractProxyTestCase
         connectorConfig.addPolicy( ProxyConnectorConfiguration.POLICY_RELEASES, releasePolicy );
         connectorConfig.addPolicy( ProxyConnectorConfiguration.POLICY_SNAPSHOTS, snapshotPolicy );
         connectorConfig.addPolicy( ProxyConnectorConfiguration.POLICY_CACHE_FAILURES, cacheFailuresPolicy );
+        connectorConfig.addPolicy( ProxyConnectorConfiguration.POLICY_PROPAGATE_ERRORS, errorPolicy );
+        connectorConfig.addPolicy( ProxyConnectorConfiguration.POLICY_PROPAGATE_ERRORS_ON_UPDATE, errorOnUpdatePolicy );
 
         int count = config.getConfiguration().getProxyConnectors().size();
         config.getConfiguration().addProxyConnector( connectorConfig );
@@ -318,6 +337,10 @@ public abstract class AbstractProxyTestCase
         config.triggerChange( prefix + ".policies.checksum", connectorConfig.getPolicy( "checksum", "" ) );
         config.triggerChange( prefix + ".policies.snapshots", connectorConfig.getPolicy( "snapshots", "" ) );
         config.triggerChange( prefix + ".policies.cache-failures", connectorConfig.getPolicy( "cache-failures", "" ) );
+        config.triggerChange( prefix + ".policies.propagate-errors",
+                              connectorConfig.getPolicy( "propagate-errors", "" ) );
+        config.triggerChange( prefix + ".policies.propagate-errors-on-update",
+                              connectorConfig.getPolicy( "propagate-errors-on-update", "" ) );
     }
 
     protected void saveManagedRepositoryConfig( String id, String name, String path, String layout )
diff --git a/archiva-modules/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/ErrorHandlingTest.java b/archiva-modules/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/ErrorHandlingTest.java
new file mode 100644 (file)
index 0000000..0e62f82
--- /dev/null
@@ -0,0 +1,627 @@
+package org.apache.maven.archiva.proxy;
+
+/*
+ * 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.maven.archiva.policies.CachedFailuresPolicy;
+import org.apache.maven.archiva.policies.ChecksumPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsDownloadPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsOnUpdateDownloadPolicy;
+import org.apache.maven.archiva.policies.ProxyDownloadException;
+import org.apache.maven.archiva.policies.ReleasesPolicy;
+import org.apache.maven.archiva.policies.SnapshotsPolicy;
+import org.apache.maven.archiva.repository.layout.LayoutException;
+import org.apache.maven.wagon.ResourceDoesNotExistException;
+import org.apache.maven.wagon.TransferFailedException;
+import org.apache.maven.wagon.authorization.AuthorizationException;
+
+import java.io.File;
+
+/**
+ * ErrorHandlingTest
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class ErrorHandlingTest
+    extends AbstractProxyTestCase
+{
+    private static final String PATH_IN_BOTH_REMOTES_NOT_LOCAL =
+        "org/apache/maven/test/get-in-both-proxies/1.0/get-in-both-proxies-1.0.jar";
+
+    private static final String PATH_IN_BOTH_REMOTES_AND_LOCAL =
+        "org/apache/maven/test/get-on-multiple-repos/1.0/get-on-multiple-repos-1.0.pom";
+
+    private static final String ID_MOCKED_PROXIED1 = "badproxied1";
+
+    private static final String NAME_MOCKED_PROXIED1 = "Bad Proxied 1";
+
+    private static final String ID_MOCKED_PROXIED2 = "badproxied2";
+
+    private static final String NAME_MOCKED_PROXIED2 = "Bad Proxied 2";
+
+    public void testPropagateErrorImmediatelyWithErrorThenSuccess()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP );
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED2 );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateErrorImmediatelyWithNotFoundThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED2 );
+    }
+
+    public void testPropagateErrorImmediatelyWithSuccessThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED1 );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED1 );
+    }
+
+    public void testPropagateErrorImmediatelyWithNotFoundThenSuccess()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED2 );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED2 );
+    }
+
+    public void testPropagateErrorAtEndWithErrorThenSuccess()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED2 );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateErrorAtEndWithSuccessThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED1 );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED1 );
+    }
+
+    public void testPropagateErrorAtEndWithNotFoundThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED2 );
+    }
+
+    public void testPropagateErrorAtEndWithErrorThenNotFound()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateErrorAtEndWithErrorThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmFailures( path, new String[]{ID_MOCKED_PROXIED1, ID_MOCKED_PROXIED2} );
+    }
+
+    public void testPropagateErrorAtEndWithNotFoundThenSuccess()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED2 );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED2 );
+    }
+
+    public void testIgnoreErrorWithErrorThenSuccess()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED2 );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED2 );
+    }
+
+    public void testIgnoreErrorWithSuccessThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        saveConnector( ID_DEFAULT_MANAGED, ID_PROXIED1 );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE );
+
+        confirmSuccess( path, expectedFile, REPOPATH_PROXIED1 );
+    }
+
+    public void testIgnoreErrorWithNotFoundThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+    }
+
+    public void testIgnoreErrorWithErrorThenNotFound()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        simulateGetError( path, expectedFile, createResourceNotFoundException() );
+
+        confirmNotDownloadedNoError( path );
+    }
+
+    public void testIgnoreErrorWithErrorThenError()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+    }
+
+    public void testPropagateOnUpdateAlwaysArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateOnUpdateAlwaysArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateOnUpdateAlwaysQueueArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmFailures( path, new String[] { ID_MOCKED_PROXIED1, ID_MOCKED_PROXIED2 } );
+    }
+
+    public void testPropagateOnUpdateAlwaysQueueArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmFailures( path, new String[] { ID_MOCKED_PROXIED1, ID_MOCKED_PROXIED2 } );
+    }
+
+    public void testPropagateOnUpdateAlwaysIgnoreArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+    }
+
+    public void testPropagateOnUpdateAlwaysIgnoreArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.ALWAYS );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+        assertTrue( expectedFile.exists() );
+    }
+
+    public void testPropagateOnUpdateNotPresentArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmSingleFailure( path, ID_MOCKED_PROXIED1 );
+    }
+
+    public void testPropagateOnUpdateNotPresentArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.STOP,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+        assertTrue( expectedFile.exists() );
+    }
+
+    public void testPropagateOnUpdateNotPresentQueueArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmFailures( path, new String[] { ID_MOCKED_PROXIED1, ID_MOCKED_PROXIED2 } );
+    }
+
+    public void testPropagateOnUpdateNotPresentQueueArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.QUEUE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+        assertTrue( expectedFile.exists() );
+    }
+
+    public void testPropagateOnUpdateNotPresentIgnoreArtifactNotPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_NOT_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFileNotPresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetError( path, expectedFile, createTransferException() );
+        simulateGetError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+    }
+
+    public void testPropagateOnUpdateNotPresentIgnoreArtifactPresent()
+        throws Exception
+    {
+        String path = PATH_IN_BOTH_REMOTES_AND_LOCAL;
+        File expectedFile = setupRepositoriesWithLocalFilePresent( path );
+
+        createMockedProxyConnector( ID_MOCKED_PROXIED1, NAME_MOCKED_PROXIED1, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+        createMockedProxyConnector( ID_MOCKED_PROXIED2, NAME_MOCKED_PROXIED2, PropagateErrorsDownloadPolicy.IGNORE,
+                                    PropagateErrorsOnUpdateDownloadPolicy.NOT_PRESENT );
+
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+        simulateGetIfNewerError( path, expectedFile, createTransferException() );
+
+        confirmNotDownloadedNoError( path );
+        assertTrue( expectedFile.exists() );
+    }
+
+    // ------------------------------------------
+    // HELPER METHODS
+    // ------------------------------------------
+
+    private void createMockedProxyConnector( String id, String name, String errorPolicy )
+    {
+        saveRemoteRepositoryConfig( id, name, "test://bad.machine.com/repo/", "default" );
+        saveConnector( ID_DEFAULT_MANAGED, id, ChecksumPolicy.FIX, ReleasesPolicy.ALWAYS, SnapshotsPolicy.ALWAYS,
+                       CachedFailuresPolicy.NO, errorPolicy );
+    }
+
+    private void createMockedProxyConnector( String id, String name, String errorPolicy, String errorOnUpdatePolicy )
+    {
+        saveRemoteRepositoryConfig( id, name, "test://bad.machine.com/repo/", "default" );
+        saveConnector( ID_DEFAULT_MANAGED, id, ChecksumPolicy.FIX, ReleasesPolicy.ALWAYS, SnapshotsPolicy.ALWAYS,
+                       CachedFailuresPolicy.NO, errorPolicy, errorOnUpdatePolicy );
+    }
+
+    private File setupRepositoriesWithLocalFileNotPresent( String path )
+        throws Exception
+    {
+        setupTestableManagedRepository( path );
+
+        File file = new File( managedDefaultDir, path );
+
+        assertNotExistsInManagedDefaultRepo( file );
+
+        return file;
+    }
+
+    private File setupRepositoriesWithLocalFilePresent( String path )
+        throws Exception
+    {
+        setupTestableManagedRepository( path );
+
+        File file = new File( managedDefaultDir, path );
+
+        assertTrue( file.exists() );
+
+        return file;
+    }
+
+    private void simulateGetError( String path, File expectedFile, Exception throwable )
+        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
+    {
+        wagonMock.get( path, createExpectedTempFile( expectedFile ) );
+        wagonMockControl.setThrowable( throwable, 1 );
+    }
+
+    private void simulateGetIfNewerError( String path, File expectedFile, TransferFailedException exception )
+        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
+    {
+        wagonMock.getIfNewer( path, createExpectedTempFile( expectedFile ), expectedFile.lastModified() );
+        wagonMockControl.setThrowable( exception, 1 );
+    }
+
+    private File createExpectedTempFile( File expectedFile )
+    {
+        return new File( expectedFile.getParentFile(), expectedFile.getName() + ".tmp" ).getAbsoluteFile();
+    }
+
+    private void confirmSingleFailure( String path, String id )
+        throws LayoutException
+    {
+        confirmFailures( path, new String[]{id} );
+    }
+
+    private void confirmFailures( String path, String[] ids )
+        throws LayoutException
+    {
+        wagonMockControl.replay();
+
+        // Attempt the proxy fetch.
+        File downloadedFile = null;
+        try
+        {
+            downloadedFile = proxyHandler.fetchFromProxies( managedDefaultRepository,
+                                                            managedDefaultRepository.toArtifactReference( path ) );
+            fail( "Proxy should not have succeeded" );
+        }
+        catch ( ProxyDownloadException e )
+        {
+            assertEquals( ids.length, e.getFailures().size() );
+            for ( String id : ids )
+            {
+                assertTrue( e.getFailures().keySet().contains( id ) );
+            }
+        }
+
+        wagonMockControl.verify();
+
+        assertNotDownloaded( downloadedFile );
+    }
+
+    private void confirmSuccess( String path, File expectedFile, String basedir )
+        throws Exception
+    {
+        File downloadedFile = performDownload( path );
+
+        File proxied1File = new File( basedir, path );
+        assertFileEquals( expectedFile, downloadedFile, proxied1File );
+    }
+
+    private void confirmNotDownloadedNoError( String path )
+        throws Exception
+    {
+        File downloadedFile = performDownload( path );
+
+        assertNotDownloaded( downloadedFile );
+    }
+
+    private File performDownload( String path )
+        throws ProxyDownloadException, LayoutException
+    {
+        wagonMockControl.replay();
+
+        // Attempt the proxy fetch.
+        File downloadedFile = proxyHandler.fetchFromProxies( managedDefaultRepository,
+                                                             managedDefaultRepository.toArtifactReference( path ) );
+
+        wagonMockControl.verify();
+        return downloadedFile;
+    }
+
+    private static TransferFailedException createTransferException()
+    {
+        return new TransferFailedException( "test download exception" );
+    }
+
+    private static ResourceDoesNotExistException createResourceNotFoundException()
+    {
+        return new ResourceDoesNotExistException( "test download not found" );
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ErrorHandlingTest.xml b/archiva-modules/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ErrorHandlingTest.xml
new file mode 100644 (file)
index 0000000..508b58b
--- /dev/null
@@ -0,0 +1,118 @@
+<!--
+  ~ 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.
+  -->
+
+<component-set>
+  <components>
+    <component>
+      <role>org.apache.maven.wagon.Wagon</role>
+      <role-hint>test</role-hint>
+      <implementation>org.apache.maven.archiva.proxy.WagonDelegate</implementation>
+    </component>
+    <component>
+      <role>org.apache.maven.archiva.configuration.ArchivaConfiguration</role>
+      <role-hint>mock</role-hint>
+      <implementation>org.apache.maven.archiva.proxy.MockConfiguration</implementation>
+    </component>
+    <component>
+      <role>org.apache.maven.archiva.repository.RepositoryContentFactory</role>
+      <role-hint>mocked</role-hint>
+      <implementation>org.apache.maven.archiva.repository.RepositoryContentFactory</implementation>
+      <description>RepositoryContentRequest</description>
+      <requirements>
+        <requirement>
+          <role>org.apache.maven.archiva.configuration.ArchivaConfiguration</role>
+          <role-hint>mock</role-hint>
+          <field-name>archivaConfiguration</field-name>
+        </requirement>
+      </requirements>
+    </component>
+    <component>
+      <role>org.apache.maven.archiva.proxy.RepositoryProxyConnectors</role>
+      <role-hint>default</role-hint>
+      <implementation>org.apache.maven.archiva.proxy.DefaultRepositoryProxyConnectors</implementation>
+      <description>DefaultRepositoryProxyConnectors</description>
+      <requirements>
+        <requirement>
+          <role>org.apache.maven.archiva.configuration.ArchivaConfiguration</role>
+          <role-hint>mock</role-hint>
+          <field-name>archivaConfiguration</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.wagon.Wagon</role>
+          <field-name>wagons</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.repository.RepositoryContentFactory</role>
+          <role-hint>mocked</role-hint>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.repository.metadata.MetadataTools</role>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.policies.PreDownloadPolicy</role>
+          <field-name>preDownloadPolicies</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.policies.PostDownloadPolicy</role>
+          <field-name>postDownloadPolicies</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.policies.DownloadErrorPolicy</role>
+          <field-name>downloadErrorPolicies</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+          <role-hint>default</role-hint>
+          <field-name>urlFailureCache</field-name>
+        </requirement>
+        <requirement>
+          <role>org.apache.maven.archiva.repository.scanner.RepositoryContentConsumers</role>
+          <field-name>consumers</field-name>
+        </requirement>
+      </requirements>
+    </component>
+    
+    <component>
+      <role>org.codehaus.plexus.cache.Cache</role>
+      <role-hint>url-failures-cache</role-hint>
+      <implementation>org.codehaus.plexus.cache.ehcache.EhcacheCache</implementation>
+      <description>URL Failure Cache</description>
+      <configuration>
+        <disk-expiry-thread-interval-seconds>600</disk-expiry-thread-interval-seconds>
+        <disk-persistent>false</disk-persistent> <!--disabling disk persistence for unit testing. -->
+        <disk-store-path>${java.io.tmpdir}/archiva/urlcache</disk-store-path>
+        <eternal>false</eternal>
+        <max-elements-in-memory>1000</max-elements-in-memory>
+        <memory-eviction-policy>LRU</memory-eviction-policy>
+        <name>url-failures-cache</name>
+        <overflow-to-disk>false</overflow-to-disk>
+        <!-- 45 minutes = 2700 seconds -->
+        <time-to-idle-seconds>2700</time-to-idle-seconds>
+        <!-- 30 minutes = 1800 seconds  -->
+        <time-to-live-seconds>1800</time-to-live-seconds>
+      </configuration>
+    </component>    
+    
+    <component>
+      <role>org.codehaus.plexus.logging.LoggerManager</role>
+      <implementation>org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager</implementation>
+      <lifecycle-handler>basic</lifecycle-handler>
+    </component>
+  </components>
+</component-set>
\ No newline at end of file
index 68adb6c57133d8d77d24ba60eeda794178672c50..1318f5765c0574aeb363672c9a7fe7f0beb29afd 100644 (file)
@@ -22,7 +22,8 @@ package org.apache.maven.archiva.web.action.admin.connectors.proxy;
 import com.opensymphony.xwork.Preparable;
 import org.apache.commons.lang.StringUtils;
 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
-import org.apache.maven.archiva.policies.DownloadPolicy;
+import org.apache.maven.archiva.policies.DownloadErrorPolicy;
+import org.apache.maven.archiva.policies.Policy;
 import org.apache.maven.archiva.policies.PostDownloadPolicy;
 import org.apache.maven.archiva.policies.PreDownloadPolicy;
 
@@ -54,6 +55,11 @@ public abstract class AbstractProxyConnectorFormAction
      */
     private Map<String, PostDownloadPolicy> postDownloadPolicyMap;
 
+    /**
+     * @plexus.requirement role="org.apache.maven.archiva.policies.DownloadErrorPolicy"
+     */
+    private Map<String, DownloadErrorPolicy> downloadErrorPolicyMap;
+
     /**
      * The list of network proxy ids that are available.
      */
@@ -72,7 +78,7 @@ public abstract class AbstractProxyConnectorFormAction
     /**
      * The map of policies that are available to be set.
      */
-    private Map<String, DownloadPolicy> policyMap;
+    private Map<String, Policy> policyMap;
 
     /**
      * The property key to add or remove.
@@ -185,7 +191,7 @@ public abstract class AbstractProxyConnectorFormAction
         return pattern;
     }
 
-    public Map<String, DownloadPolicy> getPolicyMap()
+    public Map<String, Policy> getPolicyMap()
     {
         return policyMap;
     }
@@ -318,7 +324,7 @@ public abstract class AbstractProxyConnectorFormAction
         this.pattern = pattern;
     }
 
-    public void setPolicyMap( Map<String, DownloadPolicy> policyMap )
+    public void setPolicyMap( Map<String, Policy> policyMap )
     {
         this.policyMap = policyMap;
     }
@@ -363,12 +369,13 @@ public abstract class AbstractProxyConnectorFormAction
         return options;
     }
 
-    protected Map<String, DownloadPolicy> createPolicyMap()
+    protected Map<String, Policy> createPolicyMap()
     {
-        Map<String, DownloadPolicy> policyMap = new HashMap<String, DownloadPolicy>();
+        Map<String, Policy> policyMap = new HashMap<String, Policy>();
 
         policyMap.putAll( preDownloadPolicyMap );
         policyMap.putAll( postDownloadPolicyMap );
+        policyMap.putAll( downloadErrorPolicyMap );
 
         return policyMap;
     }
@@ -387,10 +394,10 @@ public abstract class AbstractProxyConnectorFormAction
         else
         {
             // Validate / Fix policy settings arriving from browser.
-            for ( Map.Entry<String, DownloadPolicy> entry : getPolicyMap().entrySet() )
+            for ( Map.Entry<String, Policy> entry : getPolicyMap().entrySet() )
             {
-                String policyId = (String) entry.getKey();
-                DownloadPolicy policy = (DownloadPolicy) entry.getValue();
+                String policyId = entry.getKey();
+                Policy policy = entry.getValue();
                 List<String> options = policy.getOptions();
 
                 if ( !connector.getPolicies().containsKey( policyId ) )
index bafea90afd4494569aee0a38d4a0a40b16f431f9..da8b31f5c90a6c9e23e3e6f744b470ea4071407e 100644 (file)
@@ -23,7 +23,7 @@ import org.apache.maven.archiva.common.utils.PathUtil;
 import org.apache.maven.archiva.model.ArtifactReference;
 import org.apache.maven.archiva.model.ProjectReference;
 import org.apache.maven.archiva.model.VersionedReference;
-import org.apache.maven.archiva.proxy.ProxyException;
+import org.apache.maven.archiva.policies.ProxyDownloadException;
 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
 import org.apache.maven.archiva.repository.RepositoryContentFactory;
@@ -364,7 +364,7 @@ public class ProxiedDavServer
         {
             /* eat it */
         }
-        catch ( ProxyException e )
+        catch ( ProxyDownloadException e )
         {
             throw new ServletException( "Unable to fetch artifact resource.", e );
         }
@@ -391,10 +391,6 @@ public class ProxiedDavServer
         {
             /* eat it */
         }
-        catch ( ProxyException e )
-        {
-            throw new ServletException( "Unable to fetch versioned metadata resource.", e );
-        }
 
         try
         {
@@ -409,10 +405,6 @@ public class ProxiedDavServer
         {
             /* eat it */
         }
-        catch ( ProxyException e )
-        {
-            throw new ServletException( "Unable to fetch project metadata resource.", e );
-        }
 
         return false;
     }
@@ -428,7 +420,7 @@ public class ProxiedDavServer
      * artifact.
      */
     protected void applyServerSideRelocation( ArtifactReference artifact )
-        throws ProxyException
+        throws ProxyDownloadException
     {
         if ( "pom".equals( artifact.getType() ) )
         {
index 52acf5881ce24b8cae630b22ad1974d9b24c3450..6e04cee2daebefc35c8ed8fa79d5bf4bae590242 100644 (file)
@@ -42,7 +42,7 @@
         <tr>
           <td>
             <ww:label for="policy_${policy.key}" required="true"
-                      theme="simple">${policy.key}:
+                      theme="simple">${policy.value.name}:
             </ww:label>
           </td>
           <td>
index b4747e393ed28681df915263891e41bc08e2b25e..213ce9fbb051847f16ac653bf5229576d43773d9 100644 (file)
@@ -28,6 +28,8 @@ import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
 import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
 import org.apache.maven.archiva.policies.ChecksumPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsDownloadPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsOnUpdateDownloadPolicy;
 import org.apache.maven.archiva.policies.ReleasesPolicy;
 import org.apache.maven.archiva.policies.SnapshotsPolicy;
 import org.apache.maven.archiva.web.action.AbstractWebworkTestCase;
@@ -36,6 +38,7 @@ import org.codehaus.plexus.registry.RegistryException;
 import org.easymock.MockControl;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * AddProxyConnectorActionTest 
@@ -390,10 +393,13 @@ public class AddProxyConnectorActionTest
         connector.setTargetRepoId( "central" );
 
         // TODO: Set these options programatically via list of available policies.
-        connector.getPolicies().put( "releases", new ReleasesPolicy().getDefaultOption() );
-        connector.getPolicies().put( "snapshots", new SnapshotsPolicy().getDefaultOption() );
-        connector.getPolicies().put( "checksum", new ChecksumPolicy().getDefaultOption() );
-        connector.getPolicies().put( "cache-failures", new CachedFailuresPolicy().getDefaultOption() );
+        Map<String, String> policies = connector.getPolicies();
+        policies.put( "releases", new ReleasesPolicy().getDefaultOption() );
+        policies.put( "snapshots", new SnapshotsPolicy().getDefaultOption() );
+        policies.put( "checksum", new ChecksumPolicy().getDefaultOption() );
+        policies.put( "cache-failures", new CachedFailuresPolicy().getDefaultOption() );
+        policies.put( "propagate-errors", new PropagateErrorsDownloadPolicy().getDefaultOption() );
+        policies.put( "propagate-errors-on-update", new PropagateErrorsOnUpdateDownloadPolicy().getDefaultOption() );
     }
 
     @Override
index 626cf5654a791ad91ec86c065454bb455200e726..31364f4b8a558ce9dee485f2c855c53ad081a7fd 100644 (file)
@@ -28,6 +28,8 @@ import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
 import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
 import org.apache.maven.archiva.policies.ChecksumPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsDownloadPolicy;
+import org.apache.maven.archiva.policies.PropagateErrorsOnUpdateDownloadPolicy;
 import org.apache.maven.archiva.policies.ReleasesPolicy;
 import org.apache.maven.archiva.policies.SnapshotsPolicy;
 import org.apache.maven.archiva.web.action.AbstractWebworkTestCase;
@@ -36,6 +38,7 @@ import org.codehaus.plexus.registry.RegistryException;
 import org.easymock.MockControl;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * EditProxyConnectorActionTest 
@@ -395,10 +398,13 @@ public class EditProxyConnectorActionTest
         connector.setTargetRepoId( TEST_TARGET_ID );
         
         // TODO: Set these options programatically via list of available policies.
-        connector.getPolicies().put( "releases", new ReleasesPolicy().getDefaultOption() );
-        connector.getPolicies().put( "snapshots", new SnapshotsPolicy().getDefaultOption() );
-        connector.getPolicies().put( "checksum", new ChecksumPolicy().getDefaultOption() );
-        connector.getPolicies().put( "cache-failures", new CachedFailuresPolicy().getDefaultOption() );
+        Map<String, String> policies = connector.getPolicies();
+        policies.put( "releases", new ReleasesPolicy().getDefaultOption() );
+        policies.put( "snapshots", new SnapshotsPolicy().getDefaultOption() );
+        policies.put( "checksum", new ChecksumPolicy().getDefaultOption() );
+        policies.put( "cache-failures", new CachedFailuresPolicy().getDefaultOption() );
+        policies.put( "propagate-errors", new PropagateErrorsDownloadPolicy().getDefaultOption() );
+        policies.put( "propagate-errors-on-update", new PropagateErrorsOnUpdateDownloadPolicy().getDefaultOption() );
 
         config.addProxyConnector( connector );