aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--archiva-base/archiva-common/pom.xml20
-rw-r--r--archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java2
-rw-r--r--archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/ArchivaConfigurationTest.java26
-rw-r--r--archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/MavenProxyPropertyLoaderTest.java16
-rw-r--r--archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/maven/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java4
-rw-r--r--archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java6
-rw-r--r--archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java4
-rw-r--r--archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java4
-rw-r--r--archiva-base/archiva-dependency-graph/src/test/java/org/apache/maven/archiva/dependency/graph/AllTests.java4
-rw-r--r--archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaArtifactTest.java13
-rw-r--r--archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaModelClonerTest.java5
-rw-r--r--archiva-base/archiva-policies/pom.xml5
-rw-r--r--archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java13
-rw-r--r--archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/urlcache/DefaultUrlFailureCache.java10
-rw-r--r--archiva-base/archiva-policies/src/main/resources/META-INF/spring-context.xml12
-rw-r--r--archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/CachedFailuresPolicyTest.java21
-rw-r--r--archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ChecksumPolicyTest.java16
-rw-r--r--archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ReleasePolicyTest.java6
-rw-r--r--archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/SnapshotsPolicyTest.java8
-rw-r--r--archiva-base/archiva-policies/src/test/resources/log4j.xml8
-rw-r--r--archiva-base/archiva-proxy/pom.xml5
-rw-r--r--archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java10
-rw-r--r--archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java62
-rw-r--r--archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.java24
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml19
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml15
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml15
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml15
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml21
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml15
-rw-r--r--archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml15
-rw-r--r--archiva-base/archiva-repository-layer/pom.xml5
-rw-r--r--archiva-base/archiva-repository-layer/src/test/java/org/apache/maven/archiva/repository/AbstractRepositoryLayerTestCase.java19
-rw-r--r--archiva-database/src/test/java/org/apache/maven/archiva/database/AbstractArchivaDatabaseTestCase.java4
-rw-r--r--archiva-database/src/test/java/org/apache/maven/archiva/database/browsing/RepositoryBrowsingTest.java2
-rw-r--r--archiva-database/src/test/java/org/apache/maven/archiva/database/updater/DatabaseConsumersTest.java4
-rw-r--r--archiva-reporting/archiva-artifact-reports/src/test/java/org/apache/maven/archiva/reporting/artifact/AbstractArtifactReportsTestCase.java4
-rw-r--r--archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaDatabaseUpdateTaskExecutorTest.java4
-rw-r--r--archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaRepositoryScanningTaskExecutorTest.java4
-rw-r--r--archiva-web/archiva-security/src/test/java/org/apache/maven/archiva/security/SecurityStartupTest.java4
-rw-r--r--archiva-web/archiva-webapp/pom.xml17
-rw-r--r--archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java2
-rw-r--r--archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java37
-rw-r--r--archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java98
-rw-r--r--archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/ArchivaStartup.java46
-rw-r--r--archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/Banner.java10
-rw-r--r--archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml32
-rw-r--r--archiva-web/archiva-webapp/src/main/resources/webwork.properties2
-rw-r--r--archiva-web/archiva-webapp/src/main/webapp/WEB-INF/applicationContext.xml14
-rw-r--r--archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml46
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java5
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java7
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java5
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java12
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java5
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java7
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java5
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java4
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java43
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java2
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java2
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java7
-rw-r--r--archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java2
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml4
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/log4j.xml8
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml2
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml2
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml2
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml2
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml4
-rw-r--r--archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml13
-rw-r--r--archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml45
-rw-r--r--archiva-web/archiva-webdav/README-it.could-webdav.txt8
-rw-r--r--archiva-web/archiva-webdav/pom.xml101
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java125
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java214
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java48
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java37
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java274
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html61
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java1070
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java901
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html12
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java805
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java474
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java559
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html29
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gifbin0 -> 18130 bytes
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf884
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/util/package.html11
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java132
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java165
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java46
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java86
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java41
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java149
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java56
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java187
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java92
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java164
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java514
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java280
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java280
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java420
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java113
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java71
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java54
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java144
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java79
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java57
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java80
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java52
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java122
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java55
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java72
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html12
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html134
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java242
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java131
-rw-r--r--archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html12
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java159
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java143
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java51
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java39
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java74
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java88
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java164
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java39
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java67
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java142
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java119
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java137
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java130
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java303
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java185
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java191
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java66
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java184
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java57
-rw-r--r--archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html15
-rw-r--r--archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt128
-rw-r--r--archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types127
-rw-r--r--archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props13
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java495
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java67
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java38
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java38
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java38
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java51
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java255
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java203
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java148
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java401
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java267
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java46
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java42
-rw-r--r--archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java75
-rw-r--r--archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml38
-rw-r--r--archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml38
-rw-r--r--archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml38
-rw-r--r--archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml38
-rw-r--r--archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml43
-rw-r--r--archiva-web/pom.xml1
-rw-r--r--pom.xml32
164 files changed, 15194 insertions, 435 deletions
diff --git a/archiva-base/archiva-common/pom.xml b/archiva-base/archiva-common/pom.xml
index d520be144..ff8a9c5eb 100644
--- a/archiva-base/archiva-common/pom.xml
+++ b/archiva-base/archiva-common/pom.xml
@@ -30,7 +30,7 @@
<dependencies>
<!-- TO OTHER DEVELOPERS:
This module should depend on NO OTHER ARCHIVA MODULES.
- If you feel tempted to add one, discuss it first in the
+ If you feel tempted to add one, discuss it first in the
archiva-dev@maven.apache.org mailing-list.
joakime@apache.org
-->
@@ -56,7 +56,23 @@
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-container-default</artifactId>
+ <artifactId>plexus-slf4j-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>xalan</groupId>
+ <artifactId>xalan</artifactId>
+ <version>2.7.0</version>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>1.6.1</version>
+ <scope>test</scope>
</dependency>
</dependencies>
<build>
diff --git a/archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java b/archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java
index 4c9259a27..28ceeaa7b 100644
--- a/archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java
+++ b/archiva-base/archiva-configuration/src/main/java/org/apache/maven/archiva/configuration/DefaultArchivaConfiguration.java
@@ -102,11 +102,13 @@ public class DefaultArchivaConfiguration
/**
* @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
+ * @todo these don't strictly belong in here
*/
private Map<String, PreDownloadPolicy> prePolicies;
/**
* @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
+ * @todo these don't strictly belong in here
*/
private Map<String, PostDownloadPolicy> postPolicies;
diff --git a/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/ArchivaConfigurationTest.java b/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/ArchivaConfigurationTest.java
index ab6b80c38..d96965c98 100644
--- a/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/ArchivaConfigurationTest.java
+++ b/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/ArchivaConfigurationTest.java
@@ -19,24 +19,34 @@ package org.apache.maven.archiva.configuration;
* under the License.
*/
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
import org.apache.commons.io.FileUtils;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.registry.RegistryException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.custommonkey.xmlunit.XMLAssert;
import org.easymock.MockControl;
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
/**
* Test the configuration store.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
*/
public class ArchivaConfigurationTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
+ /**
+ * {@inheritDoc}
+ * @see org.codehaus.plexus.spring.PlexusInSpringTestCase#getSpringConfigLocation()
+ */
+ protected String getSpringConfigLocation()
+ throws Exception
+ {
+ return "org/apache/maven/archiva/configuration/spring-context.xml";
+ }
+
public void testGetConfigurationFromRegistryWithASingleNamedConfigurationResource()
throws Exception
{
@@ -474,7 +484,7 @@ public class ArchivaConfigurationTest
(ArchivaConfiguration) lookup( ArchivaConfiguration.class.getName(), "test-not-allowed-to-write-to-user" );
Configuration config = archivaConfiguration.getConfiguration();
archivaConfiguration.save( config );
- // No Exception == test passes.
+ // No Exception == test passes.
// Expected Path is: Should not have thrown an exception.
}
@@ -538,7 +548,7 @@ public class ArchivaConfigurationTest
archivaConfiguration.save( configuration );
// Release existing
- release( archivaConfiguration );
+// FIXME spring equivalent ? release( archivaConfiguration );
// Reload.
archivaConfiguration =
diff --git a/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/MavenProxyPropertyLoaderTest.java b/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/MavenProxyPropertyLoaderTest.java
index db3049277..8bfc6e81d 100644
--- a/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/MavenProxyPropertyLoaderTest.java
+++ b/archiva-base/archiva-configuration/src/test/java/org/apache/maven/archiva/configuration/MavenProxyPropertyLoaderTest.java
@@ -19,22 +19,32 @@ package org.apache.maven.archiva.configuration;
* under the License.
*/
-import org.codehaus.plexus.PlexusTestCase;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
/**
* @author Edwin Punzalan
*/
public class MavenProxyPropertyLoaderTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private MavenProxyPropertyLoader loader;
+ /**
+ * {@inheritDoc}
+ * @see org.codehaus.plexus.spring.PlexusInSpringTestCase#getSpringConfigLocation()
+ */
+ protected String getSpringConfigLocation()
+ throws Exception
+ {
+ return "org/apache/maven/archiva/configuration/spring-context.xml";
+ }
+
public void testLoadValidMavenProxyConfiguration()
throws IOException, InvalidConfigurationException
{
diff --git a/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/maven/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java b/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/maven/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
index 623853824..828aa74c4 100644
--- a/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/maven/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
+++ b/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/maven/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
@@ -25,9 +25,9 @@ import org.apache.maven.archiva.database.ArchivaDatabaseException;
import org.apache.maven.archiva.database.ArtifactDAO;
import org.apache.maven.archiva.model.ArchivaArtifact;
import org.apache.maven.archiva.repository.ManagedRepositoryContent;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.jpox.SchemaTool;
import java.io.File;
@@ -46,7 +46,7 @@ import javax.jdo.PersistenceManagerFactory;
* @author <a href="mailto:oching@apache.org">Maria Odea Ching</a>
*/
public abstract class AbstractRepositoryPurgeTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
public static final String TEST_REPO_ID = "test-repo";
diff --git a/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java b/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java
index 098e8a0a6..51f9412bc 100644
--- a/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java
+++ b/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java
@@ -19,15 +19,15 @@ package org.apache.maven.archiva.consumers.database;
* under the License.
*/
-import org.codehaus.plexus.PlexusTestCase;
import org.apache.commons.io.FileUtils;
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
-import org.apache.maven.archiva.repository.RepositoryContentFactory;
import org.apache.maven.archiva.model.ArchivaArtifact;
import org.apache.maven.archiva.model.ArchivaArtifactModel;
import org.apache.maven.archiva.model.ArchivaProjectModel;
+import org.apache.maven.archiva.repository.RepositoryContentFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.io.File;
@@ -35,7 +35,7 @@ import java.io.File;
* @author <a href="mailto:oching@apache.org">Maria Odea Ching</a>
*/
public abstract class AbstractDatabaseCleanupTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
ArchivaConfiguration archivaConfig;
diff --git a/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java b/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java
index 64eeae2bd..22070f151 100644
--- a/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java
+++ b/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java
@@ -22,7 +22,7 @@ package org.apache.maven.archiva.consumers.lucene;
import org.apache.maven.archiva.consumers.DatabaseCleanupConsumer;
import org.apache.maven.archiva.model.ArchivaArtifact;
import org.apache.maven.archiva.model.ArchivaArtifactModel;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
/**
* LuceneCleanupRemoveIndexedConsumerTest
@@ -31,7 +31,7 @@ import org.codehaus.plexus.PlexusTestCase;
* @version
*/
public class LuceneCleanupRemoveIndexedConsumerTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private DatabaseCleanupConsumer luceneCleanupRemoveIndexConsumer;
diff --git a/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java b/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java
index 70a5ca1ff..4dbc2a434 100644
--- a/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java
+++ b/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java
@@ -26,8 +26,8 @@ import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.io.File;
import java.io.IOException;
@@ -44,7 +44,7 @@ import java.util.List;
* @todo group metadata
*/
public class RepositoryConverterTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private ArtifactRepository sourceRepository;
diff --git a/archiva-base/archiva-dependency-graph/src/test/java/org/apache/maven/archiva/dependency/graph/AllTests.java b/archiva-base/archiva-dependency-graph/src/test/java/org/apache/maven/archiva/dependency/graph/AllTests.java
index f99e786c9..24f12c12b 100644
--- a/archiva-base/archiva-dependency-graph/src/test/java/org/apache/maven/archiva/dependency/graph/AllTests.java
+++ b/archiva-base/archiva-dependency-graph/src/test/java/org/apache/maven/archiva/dependency/graph/AllTests.java
@@ -23,7 +23,7 @@ import junit.framework.Test;
import junit.framework.TestSuite;
/**
- * Utility class to aide IDE developers.
+ * Utility class to aide IDE developers.
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
@@ -39,7 +39,7 @@ public class AllTests
suite.addTestSuite( GraphvizDotTool.class );
suite.addTestSuite( DepManDeepVersionDependencyGraphTest.class );
suite.addTestSuite( SimpleDependencyGraphTest.class );
- suite.addTestSuite( MavenProjectInfoReportsPluginDependencyGraphTest.class );
+// suite.addTestSuite( MavenProjectInfoReportsPluginDependencyGraphTest.class );
suite.addTestSuite( ArchivaCommonDependencyGraphTest.class );
suite.addTestSuite( WagonManagerDependencyGraphTest.class );
suite.addTestSuite( ContinuumStoreDependencyGraphTest.class );
diff --git a/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaArtifactTest.java b/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaArtifactTest.java
index 822f59833..80f53030f 100644
--- a/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaArtifactTest.java
+++ b/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaArtifactTest.java
@@ -22,26 +22,27 @@ package org.apache.maven.archiva.model;
import java.util.Date;
import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
/**
- * ArchivaModelClonerTest
+ * ArchivaModelClonerTest
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id: ArchivaModelClonerTest.java 525951 2007-04-05 20:11:19Z joakime $
*/
public class ArchivaArtifactTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
public void testArtifactModelProcessed()
{
ArchivaArtifactModel model = new ArchivaArtifactModel();
-
+
assertNull( "whenProcessed", model.getWhenProcessed() );
assertFalse( "isProcessed", model.isProcessed() );
-
+
model.setWhenProcessed( new Date() );
-
+
assertTrue( "isProcessed", model.isProcessed() );
}
-
+
}
diff --git a/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaModelClonerTest.java b/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaModelClonerTest.java
index fc124f1a5..4f1af59da 100644
--- a/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaModelClonerTest.java
+++ b/archiva-base/archiva-model/src/test/java/org/apache/maven/archiva/model/ArchivaModelClonerTest.java
@@ -20,15 +20,16 @@ package org.apache.maven.archiva.model;
*/
import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
/**
- * ArchivaModelClonerTest
+ * ArchivaModelClonerTest
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
*/
public class ArchivaModelClonerTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
public void testCloneProjectModelWithParent()
{
diff --git a/archiva-base/archiva-policies/pom.xml b/archiva-base/archiva-policies/pom.xml
index 4e3acf2b6..d8e061667 100644
--- a/archiva-base/archiva-policies/pom.xml
+++ b/archiva-base/archiva-policies/pom.xml
@@ -33,6 +33,11 @@
<artifactId>archiva-common</artifactId>
</dependency>
<dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
diff --git a/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java b/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java
index 9f4495dc9..77bd3b0ba 100644
--- a/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java
+++ b/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/CachedFailuresPolicy.java
@@ -47,7 +47,7 @@ public class CachedFailuresPolicy
* All resource requests are allowed thru to the remote repo.
*/
public static final String NO = "no";
-
+
/**
* The YES policy setting means that the existence of old failures is checked, and will
* prevent the request from being performed against the remote repo.
@@ -55,7 +55,7 @@ public class CachedFailuresPolicy
public static final String YES = "yes";
/**
- * @plexus.requirement role-hint="default"
+ * @plexus.requirement
*/
private UrlFailureCache urlFailureCache;
@@ -72,9 +72,9 @@ public class CachedFailuresPolicy
{
if ( !options.contains( policySetting ) )
{
- // Not a valid code.
- throw new PolicyConfigurationException( "Unknown cache-failues policy setting [" + policySetting
- + "], valid settings are [" + StringUtils.join( options.iterator(), "," ) + "]" );
+ // Not a valid code.
+ throw new PolicyConfigurationException( "Unknown cache-failues policy setting [" + policySetting +
+ "], valid settings are [" + StringUtils.join( options.iterator(), "," ) + "]" );
}
if ( NO.equals( policySetting ) )
@@ -90,7 +90,8 @@ public class CachedFailuresPolicy
{
if ( urlFailureCache.hasFailedBefore( url ) )
{
- throw new PolicyViolationException( "NO to fetch, check-failures detected previous failure on url: " + url );
+ throw new PolicyViolationException(
+ "NO to fetch, check-failures detected previous failure on url: " + url );
}
}
diff --git a/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/urlcache/DefaultUrlFailureCache.java b/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/urlcache/DefaultUrlFailureCache.java
index a520a151c..cb2c66e71 100644
--- a/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/urlcache/DefaultUrlFailureCache.java
+++ b/archiva-base/archiva-policies/src/main/java/org/apache/maven/archiva/policies/urlcache/DefaultUrlFailureCache.java
@@ -28,18 +28,20 @@ import java.util.Date;
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
- *
- * @plexus.component role="org.apache.maven.archiva.policies.urlcache.UrlFailureCache"
- * role-hint="default"
*/
public class DefaultUrlFailureCache
implements UrlFailureCache
{
/**
- * @plexus.requirement role-hint="url-failures-cache"
+ * @todo spring cache instead
*/
private Cache urlCache;
+ public DefaultUrlFailureCache( Cache urlCache )
+ {
+ this.urlCache = urlCache;
+ }
+
public void cacheFailure( String url )
{
urlCache.register( url, new Date() );
diff --git a/archiva-base/archiva-policies/src/main/resources/META-INF/spring-context.xml b/archiva-base/archiva-policies/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..840223b1a
--- /dev/null
+++ b/archiva-base/archiva-policies/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="urlFailureCache" class="org.apache.maven.archiva.policies.urlcache.DefaultUrlFailureCache">
+ <!-- collaborators and configuration for this bean go here -->
+ <constructor-arg ref="cache#url-failures-cache" type="org.codehaus.plexus.cache.Cache"/>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/CachedFailuresPolicyTest.java b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/CachedFailuresPolicyTest.java
index 61e89ab3e..c7083d93d 100644
--- a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/CachedFailuresPolicyTest.java
+++ b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/CachedFailuresPolicyTest.java
@@ -19,31 +19,25 @@ package org.apache.maven.archiva.policies;
* under the License.
*/
-import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
-import org.codehaus.plexus.PlexusTestCase;
-
import java.io.File;
import java.util.Properties;
+import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
/**
- * CachedFailuresPolicyTest
+ * CachedFailuresPolicyTest
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
*/
public class CachedFailuresPolicyTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private DownloadPolicy lookupPolicy()
throws Exception
{
- return (DownloadPolicy) lookup( PreDownloadPolicy.class.getName(), "cache-failures" );
- }
-
- private UrlFailureCache lookupUrlFailureCache()
- throws Exception
- {
- return (UrlFailureCache) lookup( UrlFailureCache.class.getName(), "default" );
+ return (DownloadPolicy) lookup( PreDownloadPolicy.class, "cache-failures" );
}
private File getFile()
@@ -85,14 +79,13 @@ public class CachedFailuresPolicyTest
public void testPolicyYesInCache()
throws Exception
{
- UrlFailureCache urlFailureCache = lookupUrlFailureCache();
-
DownloadPolicy policy = lookupPolicy();
File localFile = getFile();
Properties request = createRequest();
String url = "http://a.bad.hostname.maven.org/path/to/resource.txt";
+ UrlFailureCache urlFailureCache = (UrlFailureCache) lookup( "urlFailureCache" );
urlFailureCache.cacheFailure( url );
request.setProperty( "url", url );
diff --git a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ChecksumPolicyTest.java b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ChecksumPolicyTest.java
index 335a7a72e..81f140237 100644
--- a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ChecksumPolicyTest.java
+++ b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ChecksumPolicyTest.java
@@ -19,22 +19,22 @@ package org.apache.maven.archiva.policies;
* under the License.
*/
-import org.apache.commons.io.FileUtils;
-import org.codehaus.plexus.PlexusTestCase;
-
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
/**
- * ChecksumPolicyTest
+ * ChecksumPolicyTest
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
*/
public class ChecksumPolicyTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String GOOD = "good";
@@ -166,7 +166,7 @@ public class ChecksumPolicyTest
Properties request = createRequest();
boolean actualResult;
-
+
try
{
policy.applyPolicy( ChecksumPolicy.FAIL, request, localFile );
@@ -195,7 +195,7 @@ public class ChecksumPolicyTest
Properties request = createRequest();
boolean actualResult;
-
+
try
{
policy.applyPolicy( ChecksumPolicy.FIX, request, localFile );
@@ -205,7 +205,7 @@ public class ChecksumPolicyTest
{
actualResult = false;
}
-
+
assertEquals( createMessage( ChecksumPolicy.FIX, md5State, sha1State ), expectedResult, actualResult );
// End result should be legitimate SHA1 and MD5 files.
diff --git a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ReleasePolicyTest.java b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ReleasePolicyTest.java
index 747241ca2..044c7220e 100644
--- a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ReleasePolicyTest.java
+++ b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/ReleasePolicyTest.java
@@ -20,19 +20,19 @@ package org.apache.maven.archiva.policies;
*/
import org.apache.commons.io.FileUtils;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.io.File;
import java.util.Properties;
/**
- * ReleasePolicyTest
+ * ReleasePolicyTest
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
*/
public class ReleasePolicyTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String PATH_VERSION_METADATA = "org/apache/archiva/archiva-testable/1.0-SNAPSHOT/maven-metadata.xml";
diff --git a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/SnapshotsPolicyTest.java b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/SnapshotsPolicyTest.java
index c57381a44..330db534b 100644
--- a/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/SnapshotsPolicyTest.java
+++ b/archiva-base/archiva-policies/src/test/java/org/apache/maven/archiva/policies/SnapshotsPolicyTest.java
@@ -19,12 +19,12 @@ package org.apache.maven.archiva.policies;
* under the License.
*/
-import org.apache.commons.io.FileUtils;
-import org.codehaus.plexus.PlexusTestCase;
-
import java.io.File;
import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
/**
* SnapshotsPolicyTest
*
@@ -32,7 +32,7 @@ import java.util.Properties;
* @version $Id$
*/
public class SnapshotsPolicyTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String PATH_VERSION_METADATA = "org/apache/archiva/archiva-testable/1.0-SNAPSHOT/maven-metadata.xml";
diff --git a/archiva-base/archiva-policies/src/test/resources/log4j.xml b/archiva-base/archiva-policies/src/test/resources/log4j.xml
index 901c99f33..9bb5e1e5a 100644
--- a/archiva-base/archiva-policies/src/test/resources/log4j.xml
+++ b/archiva-base/archiva-policies/src/test/resources/log4j.xml
@@ -39,6 +39,14 @@
<level value="error"/>
</logger>
+ <logger name="org.codehaus.plexus.spring">
+ <level value="warn"/>
+ </logger>
+
+ <logger name="org.springframework">
+ <level value="warn"/>
+ </logger>
+
<root>
<priority value ="info" />
<appender-ref ref="console" />
diff --git a/archiva-base/archiva-proxy/pom.xml b/archiva-base/archiva-proxy/pom.xml
index 2a12567e7..2dc32b1ae 100644
--- a/archiva-base/archiva-proxy/pom.xml
+++ b/archiva-base/archiva-proxy/pom.xml
@@ -29,6 +29,11 @@
<name>Archiva Base :: Proxy</name>
<dependencies>
<dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.maven.archiva</groupId>
<artifactId>archiva-configuration</artifactId>
</dependency>
diff --git a/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java b/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java
index 63bd89e7e..f0b867481 100644
--- a/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java
+++ b/archiva-base/archiva-proxy/src/main/java/org/apache/maven/archiva/proxy/DefaultRepositoryProxyConnectors.java
@@ -116,7 +116,7 @@ public class DefaultRepositoryProxyConnectors
private Map<String, PostDownloadPolicy> postDownloadPolicies;
/**
- * @plexus.requirement role-hint="default"
+ * @plexus.requirement
*/
private UrlFailureCache urlFailureCache;
@@ -526,10 +526,10 @@ public class DefaultRepositoryProxyConnectors
log.info( emsg );
return null;
}
-
+
Wagon wagon = null;
try
- {
+ {
RepositoryURL repoUrl = remoteRepository.getURL();
String protocol = repoUrl.getProtocol();
wagon = (Wagon) wagons.get( protocol );
@@ -618,7 +618,7 @@ public class DefaultRepositoryProxyConnectors
{
String url = remoteRepository.getURL().getUrl() + remotePath;
- // Transfer checksum does not use the policy.
+ // Transfer checksum does not use the policy.
if ( urlFailureCache.hasFailedBefore( url + type ) )
{
return;
@@ -833,7 +833,7 @@ public class DefaultRepositoryProxyConnectors
//Convert seconds to milliseconds
int timeoutInMilliseconds = remoteRepository.getRepository().getTimeout() * 1000;
-
+
//Set timeout
wagon.setTimeout(timeoutInMilliseconds);
diff --git a/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java b/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java
index 8527ade4f..c6a5f3e7f 100644
--- a/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java
+++ b/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/AbstractProxyTestCase.java
@@ -19,6 +19,17 @@ package org.apache.maven.archiva.proxy;
* under the License.
*/
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+
import org.apache.commons.io.FileUtils;
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
@@ -28,22 +39,12 @@ import org.apache.maven.archiva.policies.CachedFailuresPolicy;
import org.apache.maven.archiva.policies.ChecksumPolicy;
import org.apache.maven.archiva.policies.ReleasesPolicy;
import org.apache.maven.archiva.policies.SnapshotsPolicy;
-import org.apache.maven.archiva.policies.urlcache.UrlFailureCache;
import org.apache.maven.archiva.repository.ManagedRepositoryContent;
import org.apache.maven.wagon.Wagon;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusClassPathXmlApplicationContext;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Locale;
+import org.springframework.beans.factory.BeanFactory;
/**
* AbstractProxyTestCase
@@ -52,7 +53,7 @@ import java.util.Locale;
* @version $Id$
*/
public abstract class AbstractProxyTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
protected static final String ID_LEGACY_PROXIED = "legacy-proxied";
@@ -259,14 +260,6 @@ public abstract class AbstractProxyTestCase
return repoContent;
}
- protected UrlFailureCache lookupUrlFailureCache()
- throws Exception
- {
- UrlFailureCache failurlCache = (UrlFailureCache) lookup( UrlFailureCache.class.getName(), "default" );
- assertNotNull( "URL Failure Cache cannot be null.", failurlCache );
- return failurlCache;
- }
-
/**
* Read the first line from the checksum file, and return it (trimmed).
*/
@@ -378,6 +371,17 @@ public abstract class AbstractProxyTestCase
return repoLocation;
}
+ /**
+ * {@inheritDoc}
+ * @see org.codehaus.plexus.spring.PlexusInSpringTestCase#getConfigLocation()
+ */
+ @Override
+ protected String getSpringConfigLocation()
+ throws Exception
+ {
+ return "org/apache/maven/archiva/proxy/spring-context.xml";
+ }
+
@Override
protected void setUp()
throws Exception
@@ -493,7 +497,7 @@ public abstract class AbstractProxyTestCase
{
assertTrue( "Managed File should exist: ", managedFile.exists() );
assertTrue( "Remote File should exist: ", remoteFile.exists() );
-
+
managedFile.setLastModified( remoteFile.lastModified() + 55000 );
}
@@ -501,13 +505,13 @@ public abstract class AbstractProxyTestCase
{
assertTrue( "Managed File should exist: ", managedFile.exists() );
assertTrue( "Remote File should exist: ", remoteFile.exists() );
-
+
managedFile.setLastModified( remoteFile.lastModified() - 55000 );
}
protected void assertNotModified( File file, long expectedModificationTime )
{
- assertEquals( "File <" + file.getAbsolutePath() + "> not have been modified.",
+ assertEquals( "File <" + file.getAbsolutePath() + "> not have been modified.",
expectedModificationTime, file.lastModified() );
}
@@ -516,11 +520,11 @@ public abstract class AbstractProxyTestCase
{
String managedLegacyPath = managedLegacyDir.getCanonicalPath();
String testFile = file.getCanonicalPath();
-
+
assertTrue( "Unit Test Failure: File <" + testFile
+ "> should be have been defined within the legacy managed path of <" + managedLegacyPath + ">", testFile
.startsWith( managedLegacyPath ) );
-
+
assertFalse( "File < " + testFile + "> should not exist in managed legacy repository.", file.exists() );
}
@@ -529,11 +533,11 @@ public abstract class AbstractProxyTestCase
{
String managedDefaultPath = managedDefaultDir.getCanonicalPath();
String testFile = file.getCanonicalPath();
-
+
assertTrue( "Unit Test Failure: File <" + testFile
+ "> should be have been defined within the managed default path of <" + managedDefaultPath + ">", testFile
.startsWith( managedDefaultPath ) );
-
+
assertFalse( "File < " + testFile + "> should not exist in managed default repository.", file.exists() );
}
diff --git a/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.java b/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.java
index 73452adac..f2796727b 100644
--- a/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.java
+++ b/archiva-base/archiva-proxy/src/test/java/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.java
@@ -49,9 +49,9 @@ public class CacheFailuresTransferTest
String path = "org/apache/maven/test/get-in-second-proxy/1.0/get-in-second-proxy-1.0.jar";
File expectedFile = new File( managedDefaultDir.getAbsoluteFile(), path );
setupTestableManagedRepository( path );
-
+
assertNotExistsInManagedDefaultRepo( expectedFile );
-
+
ArtifactReference artifact = managedDefaultRepository.toArtifactReference( path );
// Configure Repository (usually done within archiva.xml configuration)
@@ -72,13 +72,13 @@ public class CacheFailuresTransferTest
File downloadedFile = proxyHandler.fetchFromProxies( managedDefaultRepository, artifact );
wagonMockControl.verify();
-
+
// Second attempt to download same artifact use cache
wagonMockControl.reset();
wagonMockControl.replay();
downloadedFile = proxyHandler.fetchFromProxies( managedDefaultRepository, artifact );
wagonMockControl.verify();
-
+
assertNotDownloaded( downloadedFile );
assertNoTempFiles( expectedFile );
}
@@ -91,7 +91,7 @@ public class CacheFailuresTransferTest
setupTestableManagedRepository( path );
assertNotExistsInManagedDefaultRepo( expectedFile );
-
+
ArtifactReference artifact = managedDefaultRepository.toArtifactReference( path );
// Configure Repository (usually done within archiva.xml configuration)
@@ -118,11 +118,11 @@ public class CacheFailuresTransferTest
wagonMock.get( path, new File( expectedFile.getParentFile(), expectedFile.getName() + ".tmp" ) );
wagonMockControl.setThrowable( new ResourceDoesNotExistException( "resource does not exist." ), 2 );
wagonMockControl.replay();
-
+
downloadedFile = proxyHandler.fetchFromProxies( managedDefaultRepository, artifact );
-
+
wagonMockControl.verify();
-
+
assertNotDownloaded( downloadedFile );
assertNoTempFiles( expectedFile );
}
@@ -156,4 +156,12 @@ public class CacheFailuresTransferTest
assertFileEquals( expectedFile, downloadedFile, proxied2File );
assertNoTempFiles( expectedFile );
}
+
+ protected UrlFailureCache lookupUrlFailureCache()
+ throws Exception
+ {
+ UrlFailureCache urlFailureCache = (UrlFailureCache) lookup( "urlFailureCache" );
+ assertNotNull( "URL Failure Cache cannot be null.", urlFailureCache );
+ return urlFailureCache;
+ }
}
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml
index d6b58680e..848a73057 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml
@@ -60,9 +60,11 @@
<requirement>
<role>org.apache.maven.archiva.repository.RepositoryContentFactory</role>
<role-hint>mocked</role-hint>
+ <field-name>repositoryFactory</field-name>
</requirement>
<requirement>
<role>org.apache.maven.archiva.repository.metadata.MetadataTools</role>
+ <field-name>metadataTools</field-name>
</requirement>
<requirement>
<role>org.apache.maven.archiva.policies.PreDownloadPolicy</role>
@@ -73,17 +75,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -103,12 +104,12 @@
<!-- 30 minutes = 1800 seconds -->
<time-to-live-seconds>1800</time-to-live-seconds>
</configuration>
- </component>
-
+ </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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml
index d678aaedc..9d90e2250 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml
@@ -73,17 +73,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -104,11 +103,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml
index d678aaedc..9d90e2250 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml
@@ -73,17 +73,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -104,11 +103,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml
index d678aaedc..9d90e2250 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml
@@ -73,17 +73,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -104,11 +103,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml
index d63f77c61..e7b75187c 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml
@@ -42,8 +42,8 @@
</requirement>
</requirements>
</component>
-
-
+
+
<component>
<role>org.apache.maven.archiva.repository.metadata.MetadataTools</role>
<implementation>org.apache.maven.archiva.repository.metadata.MetadataTools</implementation>
@@ -64,7 +64,7 @@
</requirement>
</requirements>
</component>
-
+
<component>
<role>org.apache.maven.archiva.proxy.RepositoryProxyConnectors</role>
<role-hint>default</role-hint>
@@ -96,17 +96,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -127,11 +126,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml
index d678aaedc..9d90e2250 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml
@@ -73,17 +73,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -104,11 +103,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml
index d678aaedc..9d90e2250 100644
--- a/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml
+++ b/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml
@@ -73,17 +73,16 @@
<field-name>postDownloadPolicies</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>
+ <requirement>
+ <role>org.apache.maven.archiva.policies.urlcache.UrlFailureCache</role>
+ <field-name>urlFailureCache</field-name>
+ </requirement>
</requirements>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
@@ -104,11 +103,11 @@
<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
+</component-set>
diff --git a/archiva-base/archiva-repository-layer/pom.xml b/archiva-base/archiva-repository-layer/pom.xml
index bc837346f..7877f44a6 100644
--- a/archiva-base/archiva-repository-layer/pom.xml
+++ b/archiva-base/archiva-repository-layer/pom.xml
@@ -29,6 +29,11 @@
<name>Archiva Repository Interface Layer</name>
<dependencies>
<dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.maven.archiva</groupId>
<artifactId>archiva-configuration</artifactId>
</dependency>
diff --git a/archiva-base/archiva-repository-layer/src/test/java/org/apache/maven/archiva/repository/AbstractRepositoryLayerTestCase.java b/archiva-base/archiva-repository-layer/src/test/java/org/apache/maven/archiva/repository/AbstractRepositoryLayerTestCase.java
index 3636e9dee..199e813d9 100644
--- a/archiva-base/archiva-repository-layer/src/test/java/org/apache/maven/archiva/repository/AbstractRepositoryLayerTestCase.java
+++ b/archiva-base/archiva-repository-layer/src/test/java/org/apache/maven/archiva/repository/AbstractRepositoryLayerTestCase.java
@@ -21,19 +21,30 @@ package org.apache.maven.archiva.repository;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.io.File;
/**
- * AbstractRepositoryLayerTestCase
+ * AbstractRepositoryLayerTestCase
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
*/
public abstract class AbstractRepositoryLayerTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
+ /**
+ * {@inheritDoc}
+ * @see org.codehaus.plexus.spring.PlexusInSpringTestCase#getSpringConfigLocation()
+ */
+ @Override
+ protected String getSpringConfigLocation()
+ throws Exception
+ {
+ return "org/apache/maven/archiva/repository/spring-context.xml";
+ }
+
protected ManagedRepositoryConfiguration createRepository( String id, String name, File location )
{
ManagedRepositoryConfiguration repo = new ManagedRepositoryConfiguration();
@@ -51,7 +62,7 @@ public abstract class AbstractRepositoryLayerTestCase
repo.setUrl( url );
return repo;
}
-
+
protected ManagedRepositoryContent createManagedRepositoryContent( String id, String name, File location, String layout )
throws Exception
{
diff --git a/archiva-database/src/test/java/org/apache/maven/archiva/database/AbstractArchivaDatabaseTestCase.java b/archiva-database/src/test/java/org/apache/maven/archiva/database/AbstractArchivaDatabaseTestCase.java
index be87fc857..d40fed4d8 100644
--- a/archiva-database/src/test/java/org/apache/maven/archiva/database/AbstractArchivaDatabaseTestCase.java
+++ b/archiva-database/src/test/java/org/apache/maven/archiva/database/AbstractArchivaDatabaseTestCase.java
@@ -26,9 +26,9 @@ import org.apache.maven.archiva.database.updater.TestDatabaseCleanupConsumer;
import org.apache.maven.archiva.database.updater.TestDatabaseUnprocessedConsumer;
import org.apache.maven.archiva.model.ArtifactReference;
import org.apache.maven.archiva.model.VersionedReference;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.jpox.SchemaTool;
import java.io.File;
@@ -49,7 +49,7 @@ import javax.jdo.PersistenceManagerFactory;
* @version $Id$
*/
public abstract class AbstractArchivaDatabaseTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String TIMESTAMP = "yyyy/MM/dd HH:mm:ss";
diff --git a/archiva-database/src/test/java/org/apache/maven/archiva/database/browsing/RepositoryBrowsingTest.java b/archiva-database/src/test/java/org/apache/maven/archiva/database/browsing/RepositoryBrowsingTest.java
index 8f2e6bf27..3eb408b9c 100644
--- a/archiva-database/src/test/java/org/apache/maven/archiva/database/browsing/RepositoryBrowsingTest.java
+++ b/archiva-database/src/test/java/org/apache/maven/archiva/database/browsing/RepositoryBrowsingTest.java
@@ -61,7 +61,7 @@ public class RepositoryBrowsingTest
public RepositoryBrowsing lookupBrowser()
throws Exception
{
- RepositoryBrowsing browser = (RepositoryBrowsing) lookup( RepositoryBrowsing.class.getName() );
+ RepositoryBrowsing browser = (RepositoryBrowsing) lookup( RepositoryBrowsing.class );
assertNotNull( "RepositoryBrowsing should not be null.", browser );
return browser;
}
diff --git a/archiva-database/src/test/java/org/apache/maven/archiva/database/updater/DatabaseConsumersTest.java b/archiva-database/src/test/java/org/apache/maven/archiva/database/updater/DatabaseConsumersTest.java
index 18265657a..575774283 100644
--- a/archiva-database/src/test/java/org/apache/maven/archiva/database/updater/DatabaseConsumersTest.java
+++ b/archiva-database/src/test/java/org/apache/maven/archiva/database/updater/DatabaseConsumersTest.java
@@ -20,7 +20,7 @@ package org.apache.maven.archiva.database.updater;
*/
import org.apache.commons.collections.CollectionUtils;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.util.List;
@@ -31,7 +31,7 @@ import java.util.List;
* @version $Id$
*/
public class DatabaseConsumersTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private DatabaseConsumers lookupDbConsumers()
throws Exception
diff --git a/archiva-reporting/archiva-artifact-reports/src/test/java/org/apache/maven/archiva/reporting/artifact/AbstractArtifactReportsTestCase.java b/archiva-reporting/archiva-artifact-reports/src/test/java/org/apache/maven/archiva/reporting/artifact/AbstractArtifactReportsTestCase.java
index 40630faa4..577cdba68 100644
--- a/archiva-reporting/archiva-artifact-reports/src/test/java/org/apache/maven/archiva/reporting/artifact/AbstractArtifactReportsTestCase.java
+++ b/archiva-reporting/archiva-artifact-reports/src/test/java/org/apache/maven/archiva/reporting/artifact/AbstractArtifactReportsTestCase.java
@@ -20,9 +20,9 @@ package org.apache.maven.archiva.reporting.artifact;
*/
import org.apache.maven.archiva.database.ArchivaDAO;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.jpox.SchemaTool;
import java.io.File;
@@ -41,7 +41,7 @@ import javax.jdo.PersistenceManagerFactory;
* @version $Id$
*/
public abstract class AbstractArtifactReportsTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
protected ArchivaDAO dao;
diff --git a/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaDatabaseUpdateTaskExecutorTest.java b/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaDatabaseUpdateTaskExecutorTest.java
index ebdb592c3..94f5d34bb 100644
--- a/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaDatabaseUpdateTaskExecutorTest.java
+++ b/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaDatabaseUpdateTaskExecutorTest.java
@@ -26,9 +26,9 @@ import org.apache.maven.archiva.database.ArtifactDAO;
import org.apache.maven.archiva.database.constraints.ArtifactsProcessedConstraint;
import org.apache.maven.archiva.model.ArchivaArtifact;
import org.apache.maven.archiva.scheduled.tasks.DatabaseTask;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.codehaus.plexus.taskqueue.execution.TaskExecutor;
import org.jpox.SchemaTool;
@@ -51,7 +51,7 @@ import javax.jdo.PersistenceManagerFactory;
* @version $Id:$
*/
public class ArchivaDatabaseUpdateTaskExecutorTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private TaskExecutor taskExecutor;
diff --git a/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaRepositoryScanningTaskExecutorTest.java b/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaRepositoryScanningTaskExecutorTest.java
index 8f935289f..4570ea7cf 100644
--- a/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaRepositoryScanningTaskExecutorTest.java
+++ b/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaRepositoryScanningTaskExecutorTest.java
@@ -26,9 +26,9 @@ import org.apache.maven.archiva.database.ArchivaDAO;
import org.apache.maven.archiva.database.ArtifactDAO;
import org.apache.maven.archiva.database.constraints.ArtifactsProcessedConstraint;
import org.apache.maven.archiva.scheduled.tasks.RepositoryTask;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.jdo.DefaultConfigurableJdoFactory;
import org.codehaus.plexus.jdo.JdoFactory;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.codehaus.plexus.taskqueue.execution.TaskExecutor;
import org.jpox.SchemaTool;
@@ -49,7 +49,7 @@ import javax.jdo.PersistenceManagerFactory;
* @version $Id$
*/
public class ArchivaRepositoryScanningTaskExecutorTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private TaskExecutor taskExecutor;
diff --git a/archiva-web/archiva-security/src/test/java/org/apache/maven/archiva/security/SecurityStartupTest.java b/archiva-web/archiva-security/src/test/java/org/apache/maven/archiva/security/SecurityStartupTest.java
index f3924f934..7096133cc 100644
--- a/archiva-web/archiva-security/src/test/java/org/apache/maven/archiva/security/SecurityStartupTest.java
+++ b/archiva-web/archiva-security/src/test/java/org/apache/maven/archiva/security/SecurityStartupTest.java
@@ -19,7 +19,7 @@ package org.apache.maven.archiva.security;
* under the License.
*/
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
/**
* SecurityStartupTest
@@ -28,7 +28,7 @@ import org.codehaus.plexus.PlexusTestCase;
* @version $Id$
*/
public class SecurityStartupTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private SecurityStartup secStart;
diff --git a/archiva-web/archiva-webapp/pom.xml b/archiva-web/archiva-webapp/pom.xml
index be1612e5e..38afffcb9 100644
--- a/archiva-web/archiva-webapp/pom.xml
+++ b/archiva-web/archiva-webapp/pom.xml
@@ -181,14 +181,19 @@
</dependency>
<!-- Other dependencies -->
<dependency>
- <groupId>org.codehaus.plexus.webdav</groupId>
- <artifactId>plexus-webdav-simple</artifactId>
+ <groupId>org.apache.maven.archiva</groupId>
+ <artifactId>archiva-webdav</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-xwork-integration</artifactId>
</dependency>
<dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>2.5.1</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>provided</scope>
@@ -221,6 +226,14 @@
<artifactId>xmlunit</artifactId>
</dependency>
<dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-quartz</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-taskqueue</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.plexus.redback</groupId>
<artifactId>redback-keys-memory</artifactId>
<version>${redback.version}</version>
diff --git a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java
index 2a821848b..b289188ed 100644
--- a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java
+++ b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java
@@ -19,9 +19,9 @@ package org.apache.maven.archiva.web.repository;
* under the License.
*/
+import org.apache.maven.archiva.webdav.util.MimeTypes;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
-import org.codehaus.plexus.webdav.util.MimeTypes;
import java.io.IOException;
import java.net.URL;
diff --git a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java
index 027f58f54..bafea90af 100644
--- a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java
+++ b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java
@@ -19,18 +19,6 @@ package org.apache.maven.archiva.web.repository;
* under the License.
*/
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletResponse;
-
import org.apache.maven.archiva.common.utils.PathUtil;
import org.apache.maven.archiva.model.ArtifactReference;
import org.apache.maven.archiva.model.ProjectReference;
@@ -49,24 +37,35 @@ import org.apache.maven.archiva.repository.layout.LayoutException;
import org.apache.maven.archiva.repository.metadata.MetadataTools;
import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
import org.apache.maven.archiva.security.ArchivaUser;
+import org.apache.maven.archiva.webdav.AbstractDavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.DavServerListener;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Model;
import org.apache.maven.model.Relocation;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
-import org.codehaus.plexus.webdav.AbstractDavServerComponent;
-import org.codehaus.plexus.webdav.DavServerComponent;
-import org.codehaus.plexus.webdav.DavServerException;
-import org.codehaus.plexus.webdav.DavServerListener;
-import org.codehaus.plexus.webdav.servlet.DavServerRequest;
-import org.codehaus.plexus.webdav.util.WebdavMethodUtil;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* ProxiedDavServer
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
- * @plexus.component role="org.codehaus.plexus.webdav.DavServerComponent"
+ * @plexus.component role="org.apache.maven.archiva.webdav.DavServerComponent"
* role-hint="proxied" instantiation-strategy="per-lookup"
*/
public class ProxiedDavServer
diff --git a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java
index f0fed7486..c3121a2cb 100644
--- a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java
+++ b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java
@@ -24,6 +24,11 @@ import org.apache.maven.archiva.configuration.ConfigurationEvent;
import org.apache.maven.archiva.configuration.ConfigurationListener;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.security.ArchivaRoleConstants;
+import org.apache.maven.archiva.webdav.DavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.servlet.multiplexed.MultiplexedWebDavServlet;
+import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
import org.codehaus.plexus.redback.authentication.AuthenticationException;
import org.codehaus.plexus.redback.authentication.AuthenticationResult;
import org.codehaus.plexus.redback.authorization.AuthorizationException;
@@ -33,21 +38,17 @@ import org.codehaus.plexus.redback.policy.MustChangePasswordException;
import org.codehaus.plexus.redback.system.SecuritySession;
import org.codehaus.plexus.redback.system.SecuritySystem;
import org.codehaus.plexus.redback.xwork.filter.authentication.HttpAuthenticator;
-import org.codehaus.plexus.webdav.DavServerComponent;
-import org.codehaus.plexus.webdav.DavServerException;
-import org.codehaus.plexus.webdav.DavServerManager;
-import org.codehaus.plexus.webdav.servlet.DavServerRequest;
-import org.codehaus.plexus.webdav.servlet.multiplexed.MultiplexedWebDavServlet;
-import org.codehaus.plexus.webdav.util.WebdavMethodUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
+import org.codehaus.plexus.spring.PlexusToSpringUtils;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
/**
* RepositoryServlet
@@ -69,25 +70,24 @@ public class RepositoryServlet
private ArchivaMimeTypeLoader mimeTypeLoader;
- public synchronized void initComponents()
- throws ServletException
+ public synchronized void initServers( ServletConfig servletConfig )
+ throws DavServerException
{
- super.initComponents();
-
- mimeTypeLoader = (ArchivaMimeTypeLoader) lookup( ArchivaMimeTypeLoader.class.getName() );
-
- securitySystem = (SecuritySystem) lookup( SecuritySystem.ROLE );
- httpAuth = (HttpAuthenticator) lookup( HttpAuthenticator.ROLE, "basic" );
-
- configuration = (ArchivaConfiguration) lookup( ArchivaConfiguration.class.getName() );
+ WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( servletConfig.getServletContext() );
+
+ mimeTypeLoader = (ArchivaMimeTypeLoader) wac.getBean(
+ PlexusToSpringUtils.buildSpringId( ArchivaMimeTypeLoader.class.getName() ) );
+
+ securitySystem = (SecuritySystem) wac.getBean( PlexusToSpringUtils.buildSpringId( SecuritySystem.ROLE ) );
+ httpAuth =
+ (HttpAuthenticator) wac.getBean( PlexusToSpringUtils.buildSpringId( HttpAuthenticator.ROLE, "basic" ) );
+
+ configuration = (ArchivaConfiguration) wac.getBean(
+ PlexusToSpringUtils.buildSpringId( ArchivaConfiguration.class.getName() ) );
configuration.addListener( this );
repositoryMap = configuration.getConfiguration().getManagedRepositoriesAsMap();
- }
- public synchronized void initServers( ServletConfig servletConfig )
- throws DavServerException
- {
for ( ManagedRepositoryConfiguration repo : repositoryMap.values() )
{
File repoDir = new File( repo.getLocation() );
@@ -109,45 +109,6 @@ public class RepositoryServlet
}
@Override
- public void destroy()
- {
- try
- {
- release( securitySystem );
- }
- catch ( ServletException e )
- {
- log( "Unable to release SecuritySystem : " + e.getMessage(), e );
- }
- try
- {
- release( httpAuth );
- }
- catch ( ServletException e )
- {
- log( "Unable to release HttpAuth : " + e.getMessage(), e );
- }
- try
- {
- release( configuration );
- }
- catch ( ServletException e )
- {
- log( "Unable to release ArchivaConfiguration : " + e.getMessage(), e );
- }
- try
- {
- release( mimeTypeLoader );
- }
- catch ( ServletException e )
- {
- log( "Unable to release ArchivaMimeTypeLoader : " + e.getMessage(), e );
- }
-
- super.destroy();
- }
-
- @Override
protected void service( HttpServletRequest httpRequest, HttpServletResponse httpResponse )
throws ServletException, IOException
{
@@ -271,14 +232,12 @@ public class RepositoryServlet
repositoryMap.clear();
repositoryMap.putAll( configuration.getConfiguration().getManagedRepositoriesAsMap() );
}
-
- DavServerManager davManager = getDavManager();
-
+
synchronized ( davManager )
{
// Clear out the old servers.
davManager.removeAllServers();
-
+
// Create new servers.
try
{
@@ -290,4 +249,9 @@ public class RepositoryServlet
}
}
}
+
+ ArchivaConfiguration getConfiguration()
+ {
+ return configuration;
+ }
}
diff --git a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/ArchivaStartup.java b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/ArchivaStartup.java
index 2dab098d9..82113ed76 100644
--- a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/ArchivaStartup.java
+++ b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/ArchivaStartup.java
@@ -19,55 +19,53 @@ package org.apache.maven.archiva.web.startup;
* under the License.
*/
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+
import org.apache.maven.archiva.common.ArchivaException;
import org.apache.maven.archiva.scheduled.ArchivaTaskScheduler;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+import org.codehaus.plexus.spring.PlexusToSpringUtils;
+import org.codehaus.plexus.taskqueue.execution.TaskQueueExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* ArchivaStartup - the startup of all archiva features in a deterministic order.
*
* @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
* @version $Id$
- *
- * @plexus.component
- * role="org.apache.maven.archiva.web.startup.ArchivaStartup"
- * role-hint="default"
*/
public class ArchivaStartup
- implements Initializable
+ implements ServletContextListener
{
- /**
- * @plexus.requirement role-hint="default"
- */
- private SecuritySynchronization securitySync;
-
- /**
- * @plexus.requirement role-hint="default"
- */
- private ResolverFactoryInit resolverFactory;
-
- /**
- * @plexus.requirement role-hint="default"
- */
- private ArchivaTaskScheduler taskScheduler;
+ public void contextDestroyed(ServletContextEvent arg0) {
+ }
- public void initialize()
- throws InitializationException
- {
- Banner.display( ArchivaVersion.determineVersion( this.getClass().getClassLoader() ) );
+ public void contextInitialized(ServletContextEvent arg0) {
+ WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(arg0.getServletContext());
+
+ SecuritySynchronization securitySync = (SecuritySynchronization) wac.getBean(PlexusToSpringUtils.buildSpringId(SecuritySynchronization.class));
+ ResolverFactoryInit resolverFactory = (ResolverFactoryInit) wac.getBean(PlexusToSpringUtils.buildSpringId(ResolverFactoryInit.class));
+ ArchivaTaskScheduler taskScheduler = (ArchivaTaskScheduler) wac.getBean(PlexusToSpringUtils.buildSpringId(ArchivaTaskScheduler.class));
+ TaskQueueExecutor databaseUpdateQueue = (TaskQueueExecutor) wac.getBean(PlexusToSpringUtils.buildSpringId(TaskQueueExecutor.class, "database-update"));
+ TaskQueueExecutor repositoryScanningQueue = (TaskQueueExecutor) wac.getBean(PlexusToSpringUtils.buildSpringId(TaskQueueExecutor.class, "repository-scanning"));
+ Banner banner = (Banner) wac.getBean(PlexusToSpringUtils.buildSpringId(Banner.class));
try
{
securitySync.startup();
resolverFactory.startup();
taskScheduler.startup();
+ banner.display();
}
catch ( ArchivaException e )
{
- throw new InitializationException( "Unable to properly startup archiva: " + e.getMessage(), e );
+ throw new RuntimeException( "Unable to properly startup archiva: " + e.getMessage(), e );
}
}
diff --git a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/Banner.java b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/Banner.java
index bb7fcf87e..76a5223ee 100644
--- a/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/Banner.java
+++ b/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/startup/Banner.java
@@ -222,15 +222,9 @@ public class Banner
return injectVersion( decode( encodedBanner ), version );
}
- public static void display( String version )
+ public void display()
{
- String banner = getBanner( version );
+ String banner = getBanner( ArchivaVersion.determineVersion( this.getClass().getClassLoader() ) );
LoggerFactory.getLogger( Banner.class ).info( StringUtils.repeat( "_", 25 ) + "\n" + banner );
}
-
- public void initialize()
- throws InitializationException
- {
- Banner.display( ArchivaVersion.determineVersion( this.getClass().getClassLoader() ) );
- }
}
diff --git a/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml b/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml
index aed45580b..8d42dcfd2 100644
--- a/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml
+++ b/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml
@@ -19,21 +19,6 @@
-->
<plexus>
- <load-on-start>
- <component>
- <role>org.apache.maven.archiva.web.startup.ArchivaStartup</role>
- <role-hint>default</role-hint>
- </component>
- <component>
- <role>org.codehaus.plexus.taskqueue.execution.TaskQueueExecutor</role>
- <role-hint>database-update</role-hint>
- </component>
- <component>
- <role>org.codehaus.plexus.taskqueue.execution.TaskQueueExecutor</role>
- <role-hint>repository-scanning</role-hint>
- </component>
- </load-on-start>
-
<components>
<component>
<role>org.codehaus.plexus.registry.Registry</role>
@@ -82,13 +67,16 @@
</component>
<component>
- <role>org.codehaus.plexus.webdav.DavServerManager</role>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
<role-hint>default</role-hint>
- <implementation>org.codehaus.plexus.webdav.DefaultDavServerManager</implementation>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
<description>DefaultDavServerManager</description>
- <configuration>
- <provider-hint>proxied</provider-hint>
- </configuration>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>proxied</role-hint>
+ </requirement>
+ </requirements>
</component>
<component>
@@ -200,8 +188,8 @@
</component>
<component>
- <role>org.codehaus.plexus.webdav.util.MimeTypes</role>
- <implementation>org.codehaus.plexus.webdav.util.MimeTypes</implementation>
+ <role>org.apache.maven.archiva.webdav.util.MimeTypes</role>
+ <implementation>org.apache.maven.archiva.webdav.util.MimeTypes</implementation>
<description>MimeTypes</description>
<configuration>
<resource>archiva-mime-types.txt</resource>
diff --git a/archiva-web/archiva-webapp/src/main/resources/webwork.properties b/archiva-web/archiva-webapp/src/main/resources/webwork.properties
index 02b991ff9..a73c02d3c 100644
--- a/archiva-web/archiva-webapp/src/main/resources/webwork.properties
+++ b/archiva-web/archiva-webapp/src/main/resources/webwork.properties
@@ -19,7 +19,7 @@
# define our own action mapper here
webwork.mapper.class = org.apache.maven.archiva.web.mapper.RepositoryActionMapper
-webwork.objectFactory = org.codehaus.plexus.xwork.PlexusObjectFactory
+webwork.objectFactory = org.codehaus.plexus.spring.WebWorkPlexusInSpringObjectFactory
webwork.url.includeParams = none
webwork.devMode = true
diff --git a/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/applicationContext.xml b/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/applicationContext.xml
new file mode 100644
index 000000000..f6b0fa81d
--- /dev/null
+++ b/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/applicationContext.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="loggerManager" class="org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager"
+ init-method="initialize"/>
+
+ <bean id="urlFailureCache" class="org.apache.maven.archiva.policies.urlcache.DefaultUrlFailureCache">
+ <constructor-arg ref="cache#url-failures-cache" type="org.codehaus.plexus.cache.Cache"/>
+ </bean>
+
+</beans>
diff --git a/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml b/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml
index f47d6ebc1..82d730be3 100644
--- a/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml
@@ -56,8 +56,28 @@
</filter-mapping>
<listener>
- <listener-class>org.codehaus.plexus.xwork.PlexusLifecycleListener</listener-class>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
+ <listener>
+ <!-- TODO: some Spring technique for this? -->
+ <listener-class>org.apache.maven.archiva.web.startup.ArchivaStartup</listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>contextClass</param-name>
+ <param-value>org.codehaus.plexus.spring.PlexusWebApplicationContext</param-value>
+ </context-param>
+
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ classpath*:/META-INF/plexus/components.xml
+ classpath*:/META-INF/spring-context.xml
+ /WEB-INF/classes/META-INF/plexus/application.xml
+ /WEB-INF/classes/META-INF/plexus/components.xml
+ /WEB-INF/applicationContext.xml
+ </param-value>
+ </context-param>
<servlet>
<servlet-name>RepositoryServlet</servlet-name>
@@ -72,22 +92,22 @@
</servlet-mapping>
<resource-ref>
- <res-ref-name>jdbc/users</res-ref-name>
- <res-type>javax.sql.DataSource</res-type>
- <res-auth>Container</res-auth>
- <res-sharing-scope>Shareable</res-sharing-scope>
+ <res-ref-name>jdbc/users</res-ref-name>
+ <res-type>javax.sql.DataSource</res-type>
+ <res-auth>Container</res-auth>
+ <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<resource-ref>
- <res-ref-name>jdbc/archiva</res-ref-name>
- <res-type>javax.sql.DataSource</res-type>
- <res-auth>Container</res-auth>
- <res-sharing-scope>Shareable</res-sharing-scope>
+ <res-ref-name>jdbc/archiva</res-ref-name>
+ <res-type>javax.sql.DataSource</res-type>
+ <res-auth>Container</res-auth>
+ <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<resource-ref>
- <res-ref-name>mail/Session</res-ref-name>
- <res-type>javax.mail.Session</res-type>
- <res-auth>Container</res-auth>
- <res-sharing-scope>Shareable</res-sharing-scope>
+ <res-ref-name>mail/Session</res-ref-name>
+ <res-type>javax.mail.Session</res-type>
+ <res-auth>Container</res-auth>
+ <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</web-app>
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java
index bfbd6ddbe..35f526e4f 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java
@@ -20,9 +20,8 @@ package org.apache.maven.archiva.web.action;
*/
import com.opensymphony.xwork.ActionSupport;
-
import org.apache.commons.lang.StringUtils;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import java.lang.reflect.Method;
import java.util.Collection;
@@ -35,7 +34,7 @@ import java.util.List;
* @version $Id$
*/
public abstract class AbstractWebworkTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
/**
* This is a conveinence method for mimicking how the webwork interceptors
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java
index 8a870cc5c..ed0501636 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java
@@ -20,16 +20,15 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.commons.io.FileUtils;
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.security.ArchivaRoleConstants;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.role.RoleManager;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.io.File;
@@ -42,7 +41,7 @@ import java.util.Collections;
* @version $Id$
*/
public class AddManagedRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private AddManagedRepositoryAction action;
@@ -59,7 +58,7 @@ public class AddManagedRepositoryActionTest
private File location;
@Override
- protected String getCustomConfigurationName()
+ protected String getPlexusConfigLocation()
{
return AbstractManagedRepositoriesAction.class.getName().replace( '.', '/' ) + "Test.xml";
}
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java
index c7beec7ca..5089b07a3 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java
@@ -20,13 +20,12 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.util.Collections;
@@ -38,7 +37,7 @@ import java.util.Collections;
* @version $Id$
*/
public class AddRemoteRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private AddRemoteRepositoryAction action;
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java
index c0582812c..f9e205f4c 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java
@@ -20,24 +20,20 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.IndeterminateConfigurationException;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
-
-import org.apache.maven.archiva.model.ArchivaProjectModel;
-
import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-
+import org.apache.maven.archiva.model.ArchivaProjectModel;
import org.apache.maven.archiva.security.ArchivaRoleConstants;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.role.RoleManager;
import org.codehaus.plexus.redback.role.RoleManagerException;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
import org.codehaus.plexus.registry.RegistryException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.io.File;
@@ -50,7 +46,7 @@ import java.util.Collections;
* @version $Id$
*/
public class DeleteManagedRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private DeleteManagedRepositoryAction action;
@@ -67,7 +63,7 @@ public class DeleteManagedRepositoryActionTest
private File location;
@Override
- protected String getCustomConfigurationName()
+ protected String getPlexusConfigLocation()
{
return AbstractManagedRepositoriesAction.class.getName().replace( '.', '/' ) + "Test.xml";
}
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java
index a6b5b7eaa..1457f5952 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java
@@ -20,15 +20,14 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.IndeterminateConfigurationException;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.registry.RegistryException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.util.Collections;
@@ -40,7 +39,7 @@ import java.util.Collections;
* @version $Id$
*/
public class DeleteRemoteRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String REPO_ID = "remote-repo-ident";
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java
index 407ffd710..c109a8eaa 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java
@@ -20,15 +20,14 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.security.ArchivaRoleConstants;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.role.RoleManager;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.io.File;
@@ -41,7 +40,7 @@ import java.util.Collections;
* @version $Id$
*/
public class EditManagedRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private EditManagedRepositoryAction action;
@@ -58,7 +57,7 @@ public class EditManagedRepositoryActionTest
private File location;
@Override
- protected String getCustomConfigurationName()
+ protected String getPlexusConfigLocation()
{
return AbstractManagedRepositoriesAction.class.getName().replace( '.', '/' ) + "Test.xml";
}
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java
index cd1a9018d..eb8a92920 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java
@@ -20,13 +20,12 @@ package org.apache.maven.archiva.web.action.admin.repositories;
*/
import com.opensymphony.xwork.Action;
-
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.easymock.MockControl;
import java.util.Collections;
@@ -38,7 +37,7 @@ import java.util.Collections;
* @version $Id$
*/
public class EditRemoteRepositoryActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private static final String REPO_ID = "remote-repo-ident";
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java
index 375a142d0..1b372a1da 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java
@@ -22,15 +22,15 @@ package org.apache.maven.archiva.web.action.admin.repositories;
import com.meterware.servletunit.ServletRunner;
import com.meterware.servletunit.ServletUnitClient;
import com.opensymphony.xwork.Action;
-import org.codehaus.plexus.PlexusTestCase;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionBundle;
import org.codehaus.plexus.redback.xwork.interceptor.SecureActionException;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
/**
* Test the repositories action returns the correct data.
*/
public class RepositoriesActionTest
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
private RepositoriesAction action;
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java
index 7b79e7978..bfd363edc 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java
@@ -22,22 +22,18 @@ package org.apache.maven.archiva.web.repository;
import com.meterware.httpunit.WebResponse;
import com.meterware.servletunit.ServletRunner;
import com.meterware.servletunit.ServletUnitClient;
-
+import net.sf.ehcache.CacheManager;
import org.apache.commons.io.FileUtils;
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
-import org.codehaus.plexus.PlexusConstants;
-import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
/**
* AbstractRepositoryServletTestCase
*
@@ -45,18 +41,24 @@ import javax.servlet.http.HttpSession;
* @version $Id$
*/
public abstract class AbstractRepositoryServletTestCase
- extends PlexusTestCase
+ extends PlexusInSpringTestCase
{
protected static final String REPOID_INTERNAL = "internal";
protected ServletUnitClient sc;
- protected ArchivaConfiguration archivaConfiguration;
-
protected File repoRootInternal;
private ServletRunner sr;
+ protected ArchivaConfiguration archivaConfiguration;
+
+ protected void saveConfiguration()
+ throws Exception
+ {
+ saveConfiguration( archivaConfiguration );
+ }
+
protected void assertFileContents( String expectedContents, File repoRoot, String path )
throws IOException
{
@@ -133,7 +135,7 @@ public abstract class AbstractRepositoryServletTestCase
}
}
- protected void saveConfiguration()
+ protected void saveConfiguration( ArchivaConfiguration archivaConfiguration )
throws Exception
{
archivaConfiguration.save( archivaConfiguration.getConfiguration() );
@@ -143,7 +145,7 @@ public abstract class AbstractRepositoryServletTestCase
throws Exception
{
super.setUp();
-
+
String appserverBase = getTestFile( "target/appserver-base" ).getAbsolutePath();
System.setProperty( "appserver.base", appserverBase );
@@ -156,29 +158,26 @@ public abstract class AbstractRepositoryServletTestCase
Configuration config = archivaConfiguration.getConfiguration();
config.addManagedRepository( createManagedRepository( REPOID_INTERNAL, "Internal Test Repo", repoRootInternal ) );
- saveConfiguration();
+ saveConfiguration( archivaConfiguration );
+
+ CacheManager.getInstance().removeCache( "url-failures-cache" );
- sr = new ServletRunner();
+ sr = new ServletRunner( getTestFile( "src/test/webapp/WEB-INF/web.xml" ) );
sr.registerServlet( "/repository/*", UnauthenticatedRepositoryServlet.class.getName() );
sc = sr.newClient();
- HttpSession session = sc.getSession( true );
- ServletContext servletContext = session.getServletContext();
- servletContext.setAttribute( PlexusConstants.PLEXUS_KEY, getContainer() );
}
-
+
@Override
- protected String getConfigurationName( String subname )
+ protected String getPlexusConfigLocation()
throws Exception
{
return "org/apache/maven/archiva/web/repository/RepositoryServletTest.xml";
}
-
+
@Override
protected void tearDown()
throws Exception
{
- release( archivaConfiguration );
-
if ( sc != null )
{
sc.clearContents();
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java
index 5d634e990..848e8cf73 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java
@@ -19,8 +19,8 @@ package org.apache.maven.archiva.web.repository;
* under the License.
*/
+import org.apache.maven.archiva.webdav.util.MimeTypes;
import org.codehaus.plexus.PlexusTestCase;
-import org.codehaus.plexus.webdav.util.MimeTypes;
/**
* ArchivaMimeTypesTest
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java
index 63eaf2416..d649740eb 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java
@@ -58,7 +58,7 @@ public class RepositoryServletProxiedMetadataRemoteOnlyTest
// --- Verification
assertExpectedMetadata( expectedMetadata, actualMetadata );
}
-
+
public void testGetProxiedPluginSnapshotVersionMetadataRemoteOnly()
throws Exception
{
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java
index 71ff228b0..1c5159849 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java
@@ -19,6 +19,7 @@ package org.apache.maven.archiva.web.repository;
* under the License.
*/
+import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
@@ -54,9 +55,10 @@ public class RepositoryServletTest
RepositoryServlet servlet = (RepositoryServlet) sc.newInvocation( REQUEST_PATH ).getServlet();
assertNotNull( servlet );
+ ArchivaConfiguration archivaConfiguration = servlet.getConfiguration();
Configuration c = archivaConfiguration.getConfiguration();
c.removeManagedRepository( c.findManagedRepositoryById( REPOID_INTERNAL ) );
- saveConfiguration();
+ saveConfiguration( archivaConfiguration );
ManagedRepositoryConfiguration repository = servlet.getRepository( REPOID_INTERNAL );
assertNull( repository );
@@ -68,6 +70,7 @@ public class RepositoryServletTest
RepositoryServlet servlet = (RepositoryServlet) sc.newInvocation( REQUEST_PATH ).getServlet();
assertNotNull( servlet );
+ ArchivaConfiguration archivaConfiguration = servlet.getConfiguration();
Configuration c = archivaConfiguration.getConfiguration();
ManagedRepositoryConfiguration repo = new ManagedRepositoryConfiguration();
repo.setId( NEW_REPOSITORY_ID );
@@ -79,7 +82,7 @@ public class RepositoryServletTest
}
repo.setLocation( repoRoot.getAbsolutePath() );
c.addManagedRepository( repo );
- saveConfiguration();
+ saveConfiguration( archivaConfiguration );
ManagedRepositoryConfiguration repository = servlet.getRepository( NEW_REPOSITORY_ID );
assertNotNull( repository );
diff --git a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java
index 7e5f063cf..3d630aaa6 100644
--- a/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java
+++ b/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java
@@ -19,7 +19,7 @@ package org.apache.maven.archiva.web.repository;
* under the License.
*/
-import org.codehaus.plexus.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
diff --git a/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml b/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml
index 1cb21906f..921c1cca1 100644
--- a/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml
@@ -22,8 +22,8 @@
<components>
<!-- Components that are common for all test cases -->
<component>
- <role>org.codehaus.plexus.webdav.util.MimeTypes</role>
- <implementation>org.codehaus.plexus.webdav.util.MimeTypes</implementation>
+ <role>org.apache.maven.archiva.webdav.util.MimeTypes</role>
+ <implementation>org.apache.maven.archiva.webdav.util.MimeTypes</implementation>
<description>MimeTypes</description>
<configuration>
<resource>archiva-mime-types.txt</resource>
diff --git a/archiva-web/archiva-webapp/src/test/resources/log4j.xml b/archiva-web/archiva-webapp/src/test/resources/log4j.xml
index ad3933785..a2e7ea23d 100644
--- a/archiva-web/archiva-webapp/src/test/resources/log4j.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/log4j.xml
@@ -23,8 +23,12 @@
<level value="info"/>
</logger>
- <logger name="org.codehaus.plexus.PlexusContainer">
- <level value="info"/>
+ <logger name="org.springframework">
+ <level value="error"/>
+ </logger>
+
+ <logger name="org.codehaus.plexus.spring">
+ <level value="error"/>
</logger>
<logger name="JPOX">
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml
index 6f4fb4247..924f07f7c 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml
@@ -24,7 +24,7 @@
<implementation>org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager</implementation>
<lifecycle-handler>basic</lifecycle-handler>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml
index 6f4fb4247..924f07f7c 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml
@@ -24,7 +24,7 @@
<implementation>org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager</implementation>
<lifecycle-handler>basic</lifecycle-handler>
</component>
-
+
<component>
<role>org.codehaus.plexus.cache.Cache</role>
<role-hint>url-failures-cache</role-hint>
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml
index 7273543eb..ba213da8e 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml
@@ -24,7 +24,7 @@
<implementation>org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager</implementation>
<lifecycle-handler>basic</lifecycle-handler>
</component>
-
+
<component>
<role>com.opensymphony.xwork.Action</role>
<role-hint>addManagedRepositoryAction</role-hint>
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml
index a5891aacc..43c1eae54 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml
@@ -19,7 +19,7 @@
<plexus>
<components>
- <component>
+ <component>
<role>org.codehaus.plexus.logging.LoggerManager</role>
<implementation>org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager</implementation>
<lifecycle-handler>basic</lifecycle-handler>
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml
index 15ea6ed1a..d7087095a 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml
@@ -64,9 +64,9 @@
</component>
<component>
- <role>org.codehaus.plexus.webdav.DavServerManager</role>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
<role-hint>default</role-hint>
- <implementation>org.codehaus.plexus.webdav.DefaultDavServerManager</implementation>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
<description>DefaultDavServerManager</description>
<configuration>
<provider-hint>proxied</provider-hint>
diff --git a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml
index 4f0ff5531..3b3b20396 100644
--- a/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml
+++ b/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml
@@ -64,13 +64,16 @@
</component>
<component>
- <role>org.codehaus.plexus.webdav.DavServerManager</role>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
<role-hint>default</role-hint>
- <implementation>org.codehaus.plexus.webdav.DefaultDavServerManager</implementation>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
<description>DefaultDavServerManager</description>
- <configuration>
- <provider-hint>proxied</provider-hint>
- </configuration>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>proxied</role-hint>
+ </requirement>
+ </requirements>
</component>
<component>
diff --git a/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml b/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..f47bd657f
--- /dev/null
+++ b/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ ~ 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.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <display-name>Apache Archiva</display-name>
+
+ <listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>contextClass</param-name>
+ <param-value>org.codehaus.plexus.spring.PlexusWebApplicationContext</param-value>
+ </context-param>
+
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ classpath*:/META-INF/plexus/components.xml
+ classpath*:/META-INF/spring-context.xml
+ target/test-classes/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml
+ </param-value>
+ </context-param>
+
+</web-app>
diff --git a/archiva-web/archiva-webdav/README-it.could-webdav.txt b/archiva-web/archiva-webdav/README-it.could-webdav.txt
new file mode 100644
index 000000000..5206a3429
--- /dev/null
+++ b/archiva-web/archiva-webdav/README-it.could-webdav.txt
@@ -0,0 +1,8 @@
+This library contains the patched sources to the it.could simple WebDAV library r280, licensed under the Apache License 2.0.
+
+http://could.it/main/a-simple-approach-to-webdav.html
+
+To later return to a released version (after the patches have been incorporated and released):
+- remove src/main/java/it and src/main/java/org/betaversion
+- remove <build> <resources> from the POM
+- replace the servlet-api dependency in the POM with it.could webdav.
diff --git a/archiva-web/archiva-webdav/pom.xml b/archiva-web/archiva-webdav/pom.xml
new file mode 100644
index 000000000..fa863c017
--- /dev/null
+++ b/archiva-web/archiva-webdav/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.maven.archiva</groupId>
+ <artifactId>archiva-web</artifactId>
+ <version>1.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>archiva-webdav</artifactId>
+ <name>Archiva WebDAV Provider</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-spring</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>2.5.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+<!-- We import these classes directly to be able to patch them, since this library hasn't been released in some time
+ <dependency>
+ <groupId>it.could</groupId>
+ <artifactId>webdav</artifactId>
+ <version>0.4</version>
+ </dependency>
+-->
+
+ <!-- Required by it.could classes -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>slide</groupId>
+ <artifactId>slide-webdavlib</artifactId>
+ <version>2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty</artifactId>
+ <version>6.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <!-- Required by it.could classes -->
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <directory>${project.build.sourceDirectory}</directory>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ <exclude>**/package.html</exclude>
+ <exclude>**/url.gif</exclude>
+ <exclude>**/url.pdf</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ </build>
+</project>
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java
new file mode 100644
index 000000000..17ebb7e9c
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java
@@ -0,0 +1,125 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * <p>An utility class providing various static methods operating on
+ * {@link InputStream input} and {@link OutputStream output} streams.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class StreamTools {
+
+ /** <p>Deny construction.</p> */
+ private StreamTools() { };
+
+ /**
+ * <p>Copy every byte from the specified {@link InputStream} to the specifed
+ * {@link OutputStream} and then close both of them.</p>
+ *
+ * <p>This method is equivalent to a call to the following method:
+ * {@link #copy(InputStream,OutputStream,boolean) copy(in, out, true)}.</p>
+ *
+ * @param in the {@link InputStream} to read bytes from.
+ * @param out the {@link OutputStream} to write bytes to.
+ * @return the number of bytes copied.
+ * @throws IOException if an I/O error occurred copying the data.
+ */
+ public static long copy(InputStream in, OutputStream out)
+ throws IOException {
+ return copy(in, out, true);
+ }
+
+ /**
+ * <p>Copy every byte from the specified {@link InputStream} to the specifed
+ * {@link OutputStream} and then optionally close both of them.</p>
+ *
+ * @param in the {@link InputStream} to read bytes from.
+ * @param out the {@link OutputStream} to write bytes to.
+ * @param close whether to close the streams or not.
+ * @return the number of bytes copied.
+ * @throws IOException if an I/O error occurred copying the data.
+ */
+ public static long copy(InputStream in, OutputStream out, boolean close)
+ throws IOException {
+ if (in == null) throw new NullPointerException("Null input");
+ if (out == null) throw new NullPointerException("Null output");
+
+ final byte buffer[] = new byte[4096];
+ int length = -1;
+ long total = 0;
+ while ((length = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, length);
+ total += length;
+ }
+
+ if (close) {
+ in.close();
+ out.close();
+ }
+
+ return total;
+ }
+
+ /**
+ * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
+ *
+ * @param outputStream The stream to close.
+ */
+ public static void close( OutputStream outputStream )
+ {
+ if ( outputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ outputStream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
+ *
+ * @param inputStream The stream to close.
+ */
+ public static void close( InputStream inputStream )
+ {
+ if ( inputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ inputStream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java
new file mode 100644
index 000000000..3b0eb14ea
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java
@@ -0,0 +1,214 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util;
+
+import it.could.util.encoding.Encodable;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * <p>An utility class providing various static methods operating on
+ * {@link String}s.</p>
+ *
+ * <p>This class implement the {@link Encodable} interface from which it
+ * inherits its {@link Encodable#DEFAULT_ENCODING default encoding}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class StringTools {
+
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_822 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+ /** <p>The {@link TimeZone} to use for dates.</p> */
+ private static final TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
+ /** <p>The {@link Locale} to use for dates.</p> */
+ private static final Locale LOCALE = Locale.US;
+
+ /** <p>Deny construction.</p> */
+ private StringTools() { }
+
+ /* ====================================================================== */
+ /* NUMBER AND DATE PARSING AND FORMATTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Format a {@link Number} into a {@link String} making sure that
+ * {@link NullPointerException}s are not thrown.</p>
+ *
+ * @param number the {@link Number} to format.
+ * @return a {@link String} instance or <b>null</b> if the object was null.
+ */
+ public static String formatNumber(Number number) {
+ if (number == null) return null;
+ return (number.toString());
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Long}.</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Long} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Long parseNumber(String string) {
+ if (string == null) return null;
+ try {
+ return new Long(string);
+ } catch (NumberFormatException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the HTTP/1.1 RFC.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatHttpDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the ISO 8601 specification.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatIsoDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the
+ * HTTP/1.1 RFC (<code>Mon, 31 Jan 2000 11:59:00 GMT</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseHttpDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the ISO 8601
+ * specification (<code>2000-12-31T11:59:00Z</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseIsoDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /* ====================================================================== */
+ /* STRING SPLITTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Split the specified string in two parts according to the specified
+ * delimiter, and any resulting path of zero length will be converted to
+ * <b>null</b>.</p>
+ */
+ public static String[] splitOnce(String source, char delimiter,
+ boolean noDelimReturnSecond) {
+ if (source == null) return new String[] { null, null };
+ final int position = source.indexOf(delimiter);
+ if (position < 0) { // --> first
+ if (noDelimReturnSecond) return new String[] { null, source };
+ else return new String[] { source, null };
+ } else if (position == 0) {
+ if (source.length() == 1) { // --> |
+ return new String[] { null, null };
+ } else { // --> |second
+ return new String[] { null, source.substring(1) };
+ }
+ } else {
+ final String first = source.substring(0, position);
+ if (source.length() -1 == position) { // --> first|
+ return new String[] { first, null };
+ } else { // --> first|second
+ return new String[] { first, source.substring(position + 1) };
+ }
+ }
+ }
+
+ /**
+ * <p>Split the specified string according to the specified delimiter, and
+ * any resulting path of zero length will be converted to <b>null</b>.</p>
+ */
+ public static String[] splitAll(String source, char delimiter) {
+ final List strings = new ArrayList();
+ String current = source;
+ while (current != null) {
+ String split[] = splitOnce(current, delimiter, false);
+ strings.add(split[0]);
+ current = split[1];
+ }
+ if (current != null) strings.add(current);
+ final int length = source.length();
+ if ((length > 0) && (source.charAt(length - 1) == delimiter)) {
+ strings.add(null);
+ }
+ return (String []) strings.toArray(new String[strings.size()]);
+ }
+
+ /**
+ * <p>Find the first occurrence of one of the specified delimiter characters
+ * in the specified source string.</p>
+ */
+ public static int findFirst(String source, String delimiters) {
+ final char array[] = source.toCharArray();
+ final char delim[] = delimiters.toCharArray();
+ for (int x = 0; x < array.length; x ++) {
+ for (int y = 0; y < delim.length; y ++) {
+ if (array[x] == delim[y]) return x;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java
new file mode 100644
index 000000000..2f644f9a0
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java
@@ -0,0 +1,48 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * <p>The {@link Encodable} interface describes an {@link Object} whose
+ * {@link String} representation can vary depending on the encoding used.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface Encodable extends EncodingAware {
+
+ /**
+ * <p>Return the {@link String} representation of this instance.</p>
+ *
+ * <p>This method is equivalent to a call to
+ * {@link #toString(String) toString}({@link EncodingAware#DEFAULT_ENCODING
+ * DEFAULT_ENCODING})</p>
+ */
+ public String toString();
+
+ /**
+ * <p>Return the {@link String} representation of this instance given
+ * a specific character encoding.</p>
+ *
+ * @throws UnsupportedEncodingException if the specified encoding is not
+ * supported by the platform.
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException;
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java
new file mode 100644
index 000000000..0690a175b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java
@@ -0,0 +1,37 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * <p>The {@link EncodingAware} interface describes an {@link Object} aware
+ * of multiple encodings existing withing the platform.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface EncodingAware {
+
+ /** <p>The default encoding is specified as being <code>UTF-8</code>.</p> */
+ public static final String DEFAULT_ENCODING = "UTF-8";
+
+ /** <p>The platform encoding is evaluated at runtime from the JVM.</p> */
+ public static final String PLATFORM_ENCODING =
+ new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java
new file mode 100644
index 000000000..c0c2e7adb
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java
@@ -0,0 +1,274 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.encoding;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * <p>An utility class providing various static methods dealing with
+ * encodings and {@link Encodable} objects..</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class EncodingTools implements EncodingAware {
+
+ /** <p>The Base-64 alphabet.</p> */
+ private static final char ALPHABET[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' };
+
+ /** <p>Deny construction of this class.</p> */
+ private EncodingTools() { }
+
+ /* ====================================================================== */
+ /* URL ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link String} representation of the specified
+ * {@link Encodable} object using the {@link EncodingAware#DEFAULT_ENCODING
+ * default encoding}.</p>
+ *
+ * throws NullPointerException if the {@link Encodable} was <b>null</b>.
+ */
+ public static String toString(Encodable encodable) {
+ try {
+ return encodable.toString(DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Default encoding \"" + DEFAULT_ENCODING +
+ "\" not supported by the platform";
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* URL ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>URL-encode the specified string.</p>
+ */
+ public static String urlEncode(String source, String encoding)
+ throws UnsupportedEncodingException {
+ if (source == null) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return URLEncoder.encode(source, encoding);
+ }
+
+ /**
+ * <p>URL-encode the specified string.</p>
+ */
+ public static String urlEncode(String source) {
+ if (source == null) return null;
+ try {
+ return URLEncoder.encode(source, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>URL-decode the specified string.</p>
+ */
+ public static String urlDecode(String source, String encoding)
+ throws UnsupportedEncodingException {
+ if (source == null) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return URLDecoder.decode(source, encoding);
+ }
+
+ /**
+ * <p>URL-decode the specified string.</p>
+ */
+ public static String urlDecode(String source) {
+ if (source == null) return null;
+ try {
+ return URLDecoder.decode(source, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* BASE 64 ENCODING / DECODING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Encode the specified string in base 64 using the specified
+ * encoding.</p>
+ */
+ public static final String base64Encode(String string, String encoding)
+ throws UnsupportedEncodingException {
+ /* Check the source string for null or the empty string. */
+ if (string == null) return (null);
+ if (string.length() == 0) return "";
+
+ /* Check the encoding */
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+
+ /* Prepare the buffers that we'll use to encode in Base 64 */
+ final byte bsrc[] = string.getBytes(encoding);
+ final char bdst[] = new char[(bsrc.length + 2) / 3 * 4];
+
+ /* Iterate into the source in chunks of three bytes */
+ int psrc = -1;
+ int pdst = 0;
+ int temp = 0;
+ while ((psrc = psrc + 3) < bsrc.length) {
+ /* For every three bytes processed ... */
+ temp = ((bsrc[psrc - 2] << 16) & 0xFF0000) |
+ ((bsrc[psrc - 1] << 8) & 0x00FF00) |
+ ((bsrc[psrc ] ) & 0x0000FF);
+ /* ... we append four bytes to the buffer */
+ bdst[pdst ++] = ALPHABET[(temp >> 18) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 12) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 6) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp ) & 0x3f];
+ }
+
+ /* Let's check whether we still have some bytes to encode */
+ switch (psrc - bsrc.length) {
+ case 0: /* Two bytes left to encode */
+ temp = ((bsrc[psrc - 2] & 0xFF) << 8) | (bsrc[psrc - 1] & 0xFF);
+ bdst[pdst ++] = ALPHABET[(temp >> 10) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp >> 4) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp << 2) & 0x3c];
+ bdst[pdst ++] = ALPHABET[64];
+ break;
+ case 1: /* One byte left to encode */
+ temp = (bsrc[psrc - 2] & 0xFF);
+ bdst[pdst ++] = ALPHABET[(temp >> 2) & 0x3f];
+ bdst[pdst ++] = ALPHABET[(temp << 4) & 0x30];
+ bdst[pdst ++] = ALPHABET[64];
+ bdst[pdst ++] = ALPHABET[64];
+ }
+
+ /* Convert the character array into a proper string */
+ return new String(bdst);
+ }
+
+ /**
+ * <p>Encode the specified string in base 64 using the default encoding.</p>
+ */
+ public static final String base64Encode(String string) {
+ try {
+ return (base64Encode(string, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Decode the specified base 64 string using the specified encoding.</p>
+ */
+ public static final String base64Decode(String string, String encoding)
+ throws UnsupportedEncodingException {
+ /* Check the source string for null or the empty string. */
+ if (string == null) return (null);
+ if (string.length() == 0) return "";
+
+ /* Check the encoding */
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+
+ /* Retrieve the array of characters of the source string. */
+ final char characters[] = string.toCharArray();
+
+ /* Check the length, which must be dividible by 4. */
+ if ((characters.length & 0x03) != 0)
+ throw new IllegalArgumentException("Invalid length for the "+
+ "encoded string (" + characters.length + ")");
+
+ /* The bytes array length is 3/4th of the characters array length */
+ byte bytes[] = new byte[characters.length - (characters.length >> 2)];
+
+ /*
+ * Since this might take a while check now for the last 4 characters
+ * token: it must contain at most two == and those need to be in the
+ * last two positions in the array (the only valid sequences are:
+ * "????", "???=" and "??==").
+ */
+ if (((characters[characters.length - 4] == '=') ||
+ (characters[characters.length - 3] == '=')) ||
+ ((characters[characters.length - 2] == '=') &&
+ (characters[characters.length - 1] != '='))) {
+ throw new IllegalArgumentException("Invalid pattern for last " +
+ "Base64 token in string to decode: " +
+ characters[characters.length - 4] +
+ characters[characters.length - 3] +
+ characters[characters.length - 2] +
+ characters[characters.length - 1]);
+ }
+
+ /* Translate the Base64-encoded String in chunks of 4 characters. */
+ int coff = 0;
+ int boff = 0;
+ while (coff < characters.length) {
+ boolean last = (coff == (characters.length - 4));
+ int curr = ((value(characters[coff ], last) << 0x12) |
+ (value(characters[coff + 1], last) << 0x0c) |
+ (value(characters[coff + 2], last) << 0x06) |
+ (value(characters[coff + 3], last) ));
+ bytes[boff + 2] = (byte)((curr ) & 0xff);
+ bytes[boff + 1] = (byte)((curr >> 0x08) & 0xff);
+ bytes[boff ] = (byte)((curr >> 0x10) & 0xff);
+ coff += 4;
+ boff += 3;
+ }
+
+ /* Get the real decoded string length, checking out the trailing '=' */
+ if (characters[coff - 1] == '=') boff--;
+ if (characters[coff - 2] == '=') boff--;
+
+ /* All done */
+ return (new String(bytes, 0, boff, encoding));
+ }
+
+ /**
+ * <p>Decode the specified base 64 string using the default encoding.</p>
+ */
+ public static final String base64Decode(String string) {
+ try {
+ return (base64Decode(string, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+
+ /** <p>Retrieve the offset of a character in the base 64 alphabet.</p> */
+ private static final int value(char character, boolean last) {
+ for (int x = 0; x < 64; x++) if (ALPHABET[x] == character) return (x);
+ if (last && (character == ALPHABET[65])) return(0);
+ final String message = "Character \"" + character + "\" invalid";
+ throw new IllegalArgumentException(message);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html
new file mode 100644
index 000000000..675ba3f58
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html
@@ -0,0 +1,61 @@
+<html>
+ <head>
+ <title>Encoding Utilities</title>
+ </head>
+ <body>
+ <p>
+ This package contains a number of utility classes dealing with generic
+ encoding of {@link java.lang.String}s.
+ </p>
+ <p>
+ Although this might sound useless at first (as {@link java.lang.String}s
+ do support encoding internally already), this class deals with a very
+ subtle problem encountered when merging Java {@link java.lang.String}s
+ and old byte-based (non internationalized) transports, such as
+ Base 64 and URL encoding.
+ </p>
+ <p>
+ Let's consider (as an example) the URL encoded {@link java.lang.String}
+ <code>%C2%A3 100</code> can be easily decomposed in a byte array using
+ URL decoding techniques: we would end up with the following byte array:
+ <code>0x0C2 0x0A3 0x20 0x31 0x30 0x30</code>.
+ </p>
+ <p>
+ This byte-array, though, doesn't tell us anything about how to represent
+ this as a readable and usable {@link java.lang.String} in Java. To be
+ able to convert this we have to decode it again using a charset (or an
+ encoding).
+ </p>
+ <p>
+ So, for example, if we were to decode the above mentioned byte array using
+ the <b>ISO-8859-1</b> encoding, we would obtain the string
+ &quot;<code>&Acirc;&pound; 100</code>&quot;, or in details:
+ </p>
+ <ul>
+ <li>a latin capital letter &quot;A&quot; with a circumflex accent</li>
+ <li>the pound sign</li>
+ <li>a space</li>
+ <li>the number 1</li>
+ <li>the number 0</li>
+ <li>the number 0</li>
+ </ul>
+ <p>
+ If we were to decode the same byte sequence using <b>UTF-8</b>, on the
+ other hand, we would obtain the (quite different) string
+ &quot;<code>&pound; 100</code>&quot;, or in details:
+ </p>
+ <ul>
+ <li>the pound sign</li>
+ <li>a space</li>
+ <li>the number 1</li>
+ <li>the number 0</li>
+ <li>the number 0</li>
+ </ul>
+ <p>
+ Therefore, as a conclusion, when Java {@link java.lang.String}s are
+ encoded using Base 64, URL encoding, or similar techiques, one always
+ have to remember that encoding (or decoding) must be done twice, and
+ this package provides a way to deal with this mechanism.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java
new file mode 100644
index 000000000..75884e0a2
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java
@@ -0,0 +1,1070 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.http;
+
+import it.could.util.encoding.EncodingTools;
+import it.could.util.location.Location;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>A class implementing an extremely simple HTTP 1.0 connector with
+ * basic authentication support.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class HttpClient {
+
+ /** <p>The default HTTP method to use.</p> */
+ public static final String DEFAULT_METHOD = "GET";
+
+ /* ====================================================================== */
+
+ /** <p>The byte sequence CR LF (the end of the request).</p> */
+ private static final byte CRLF[] = { 0x0d, 0x0a };
+ /** <p>The byte sequence for " HTTP/1.0\r\n" (the request signature).</p> */
+ private static final byte HTTP[] = { 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x0d, 0x0a };
+
+ /* ====================================================================== */
+
+ /** <p>The buffer used to parse lines in the response.</p> */
+ private final byte buffer[] = new byte[4096];
+ /** <p>The map of the current request headers.</p> */
+ private final Map requestHeaders = new HashMap();
+ /** <p>The map of the current response headers.</p> */
+ private final Map responseHeaders = new HashMap();
+
+ /* ====================================================================== */
+
+ /** <p>The {@link Location} pointing to the current request.</p> */
+ private Location location;
+ /** <p>The status of the current request.</p> */
+ private Status status = null;
+ /** <p>An array of acceptable statuses to verify upon connection.</p> */
+ private int acceptable[] = null;
+
+ /* ====================================================================== */
+
+ /** <p>The limited input stream associated with this request.</p> */
+ private Input xinput = null;
+ /** <p>The limited output stream associated with this request.</p> */
+ private Output xoutput = null;
+ /** <p>The socket associated with this request.</p> */
+ private Socket xsocket = null;
+
+ /* ====================================================================== */
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified location in string format.</p>
+ *
+ * @throws MalformedURLException if the location couldn't be parsed.
+ */
+ public HttpClient(String location)
+ throws MalformedURLException {
+ this.location = Location.parse(location);
+ }
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified location in string format.</p>
+ *
+ * @throws MalformedURLException if the location couldn't be parsed.
+ */
+ public HttpClient(String location, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ this.location = Location.parse(location, encoding);
+ }
+
+ /**
+ * <p>Create a new {@link HttpClient} instance associated with the
+ * specified {@link Location}.</p>
+ */
+ public HttpClient(Location location) {
+ if (location == null) throw new NullPointerException("Null location");
+ if (! location.isAbsolute())
+ throw new IllegalArgumentException("Relative location supplied");
+ if (! "http".equals(location.getSchemes().toString())) {
+ throw new IllegalArgumentException("Scheme is not HTTP");
+ }
+ this.location = location;
+ }
+
+ /* ====================================================================== */
+ /* CONNECTION VERIFICATION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Set an HTTP response status code considered to be acceptable when
+ * verifying the connection.</p>
+ */
+ public HttpClient setAcceptableStatus(int status) {
+ return this.setAcceptableStatuses(new int[] { status });
+ }
+
+ /**
+ * <p>Set an array of HTTP response status codes considered to be acceptable
+ * when verifying the connection.</p>
+ *
+ * <p>If the array is <b>null</b> status code checking is disabled.</p>
+ */
+ public HttpClient setAcceptableStatuses(int statuses[]) {
+ if (statuses == null) {
+ this.acceptable = null;
+ return this;
+ }
+ for (int x = 0; x < statuses.length; x ++) {
+ final int status = statuses[x];
+ if ((status < 100) || (status > 599))
+ throw new IllegalArgumentException("Wrong status " + status);
+ }
+ this.acceptable = statuses;
+ return this;
+ }
+
+ /* ====================================================================== */
+ /* CONNECTION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code>.</p>
+ *
+ * <p>This is equivalent to {@link #connect(boolean) connect(true)}.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect()
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, true, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code> allowing for a specified amount of
+ * content to be written into the request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(long contentLength)
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, false, contentLength);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction using the
+ * default method <code>GET</code> and optionally following redirects.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(boolean followRedirects)
+ throws IOException {
+ return this.connect(DEFAULT_METHOD, followRedirects, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method.</p>
+ *
+ * <p>This is equivalent to {@link #connect(String,boolean)
+ * connect(method, true)}.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method)
+ throws IOException {
+ return this.connect(method, true, 0);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method allowing for a specified amount of content to be
+ * written into the request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method, long contentLength)
+ throws IOException {
+ return this.connect(method, false, contentLength);
+ }
+
+ /**
+ * <p>Connect to the {@link Location} specified at construction with the
+ * specified method and optionally following redirects.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient connect(String method, boolean followRedirects)
+ throws IOException {
+ return this.connect(method, followRedirects, 0);
+ }
+
+ /**
+ * <p>Disconnect from the remote endpoint and terminate the request.</p>
+ *
+ * <p>Note that request and response headers, the resultin status and
+ * acceptable statuses are <b>not</b> cleared by this method.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient disconnect()
+ throws IOException {
+ return this.disconnect(false);
+ }
+
+ /**
+ * <p>Disconnect from the remote endpoint and terminate the request.</p>
+ *
+ * @param reset whether to reset all headers, status and acceptable response
+ * status codes or not.
+ * @return this {@link HttpClient} instance.
+ * @throws IOException if an I/O or a network error occurred.
+ */
+ public HttpClient disconnect(boolean reset)
+ throws IOException {
+ final Socket socket = this.xsocket;
+ if (socket != null) try {
+ /* Make sure that we mark this instance as being closed */
+ this.xsocket = null;
+
+ /* Close the input stream if necessary */
+ if (this.xinput != null) {
+ if (! this.xinput.closed) this.xinput.close();
+ this.xinput = null;
+ }
+
+ /* Close the output stream if necessary */
+ if (this.xoutput != null) {
+ if (! this.xoutput.closed) this.xoutput.close();
+ this.xoutput = null;
+ }
+
+ } finally {
+ /* Ensure that the socket is closed */
+ socket.close();
+ }
+
+ if (reset) {
+ this.requestHeaders.clear();
+ this.responseHeaders.clear();
+ this.status = null;
+ this.acceptable = null;
+ }
+ return this;
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL CONNECTION HANDLER */
+ /* ====================================================================== */
+
+ /**
+ * <p>Internal method actually connecting to the remote HTTP server.</p>
+ */
+ private HttpClient connect(String method, boolean redirect, long length)
+ throws IOException {
+ /* Check if (by any chance) we have been connected already */
+ if (this.xsocket != null)
+ throw new IllegalStateException("Already connected");
+
+ /* Check for both follow redirects and content length */
+ if (length < 0) throw new IOException("Negative length");
+ if ((length > 0) && redirect)
+ throw new InternalError("Can't follow redirects and write request");
+
+ /* Verify any authentication token */
+ final String userinfo = this.location.getAuthority().getUserInfo();
+ if (userinfo != null) {
+ final String encoded = EncodingTools.base64Encode(userinfo);
+ this.addRequestHeader("Authorization", "Basic " + encoded);
+ }
+
+ /* All methods in HTTP are upper case */
+ method = method.toUpperCase();
+
+ /* Make sure we close the connection at the end of the request */
+ this.addRequestHeader("Connection", "close", false);
+
+ /* The content length of the request is forced to be valid */
+ this.addRequestHeader("Content-Length", Long.toString(length), false);
+
+ /* Enter in a loop for redirections */
+ int redirs = 20;
+ while (true) {
+ /* If we have been redirected too many times, fail */
+ if ((--redirs) < 0) throw new IOException("Too many redirections");
+
+ /* Get the authority, once and for all */
+ final Location.Authority auth = this.location.getAuthority();
+
+ /* Prepare a normalized host header */
+ final String host = auth.getHost();
+ final int port = auth.getPort() < 0 ? 80 : auth.getPort();
+ this.addRequestHeader("Host", host + ":" + port, false);
+
+ /* Connect to the remote endpoint */
+ final Socket sock = new Socket(auth.getHost(), port);
+ final InputStream in = sock.getInputStream();
+ final OutputStream out = sock.getOutputStream();
+
+ /* Write the request line */
+ out.write((method + " ").getBytes("US-ASCII"));
+ out.write(this.location.getPath().toString().getBytes("US-ASCII"));
+ out.write(HTTP); /* SPACE HTTP/1.0 CR LF */
+
+ /* Write all the headers */
+ final Iterator headers = this.requestHeaders.values().iterator();
+ while (headers.hasNext()) {
+ final RequestHeader header = (RequestHeader) headers.next();
+ final Iterator values = header.values.iterator();
+ while (values.hasNext()) {
+ out.write(header.name);
+ out.write((byte []) values.next());
+ }
+ }
+
+ /* Write the final CRLF, read the status and the headers */
+ out.write(CRLF);
+ out.flush();
+
+ /* Return now if we have to write content */
+ if (length > 0) {
+ this.xsocket = sock;
+ this.xoutput = new Output(this, in, out, length);
+ this.xinput = null;
+ return this;
+ }
+
+ this.readStatusLine(in);
+ this.readHeaders(in);
+
+ /* If we have to follow redirects, let's inspect the response */
+ final int code = this.status.status;
+ if (redirect && ((code == 301) || (code == 302) || (code == 307))) {
+ final String location = this.getResponseHeader("Location");
+ if (location != null) {
+ in.close();
+ out.close();
+ sock.close();
+ this.location = this.location.resolve(location);
+ continue;
+ }
+ }
+
+ /* No further redirections, so verify if the status code is ok */
+ this.verify();
+
+ /* Evaluate the content length specified by the server */
+ final String len = this.getResponseHeader("Content-Length");
+ long bytesLength = -1;
+ if (len != null) try {
+ bytesLength = Long.parseLong(len);
+ } catch (NumberFormatException exception) {
+ /* Swallow this, be liberal in what we accept */
+ }
+
+ /* Return an output stream if the content length was not zero */
+ this.xsocket = sock;
+ this.xoutput = null;
+ this.xinput = new Input(this, in, bytesLength);
+ return this;
+ }
+ }
+
+ private void verify()
+ throws IOException {
+ /* No further redirections, sov erify if the status code is ok */
+ if (this.acceptable != null) {
+ boolean accepted = false;
+ for (int x = 0; x < this.acceptable.length; x ++) {
+ if (this.status.status != this.acceptable[x]) continue;
+ accepted = true;
+ break;
+ }
+ if (! accepted) {
+ this.disconnect();
+ throw new IOException("Connection to " + this.location +
+ " returned unacceptable status " +
+ this.status.status + " (" +
+ this.status.message + ")");
+ }
+ }
+ }
+
+ /* ====================================================================== */
+ /* INPUT / OUTPUT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return an {@link InputStream} where the content of the HTTP response
+ * can be read from.</p>
+ *
+ * @throws IllegalStateException if this instance is not connected yet, or
+ * the request body was not fully written yet.
+ */
+ public InputStream getResponseStream()
+ throws IllegalStateException {
+ if (this.xsocket == null)
+ throw new IllegalStateException("Connection not available");
+ if ((this.xoutput != null) && (this.xoutput.remaining != 0))
+ throw new IllegalStateException("Request body not fully written");
+ return this.xinput;
+ }
+
+ /**
+ * <p>Return an {@link OutputStream} where the content of the HTTP request
+ * can be written to.</p>
+ *
+ * @throws IllegalStateException if this instance is not connected yet or if
+ * upon connection the size of the request was
+ * not specifed or <b>zero</b>.
+ */
+ public OutputStream getRequestStream()
+ throws IllegalStateException {
+ if (this.xsocket == null)
+ throw new IllegalStateException("Connection not available");
+ if (this.xoutput == null)
+ throw new IllegalStateException("No request body to write to");
+ return this.xoutput;
+ }
+
+ /* ====================================================================== */
+ /* REQUEST AND RESPONSE METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link Location} of this connection.</p>
+ *
+ * <p>This might be different from the {@link Location} specified at
+ * construction time if upon connecting HTTP redirections were followed.</p>
+ */
+ public Location getLocation() {
+ return this.location;
+ }
+
+ /**
+ * <p>Add a new header that will be sent with the HTTP request.</p>
+ *
+ * <p>This method will remove any header value previously associated with
+ * the specified name, in other words this method is equivalent to
+ * {@link #addRequestHeader(String, String, boolean)
+ * addRequestHeader(name, value, false)}.</p>
+ *
+ * @param name the name of the request header to add.
+ * @param value the value of the request header to add.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name or value were <b>null</b>.
+ */
+ public HttpClient addRequestHeader(String name, String value) {
+ return this.addRequestHeader(name, value, false);
+ }
+
+ /**
+ * <p>Add a new header that will be sent with the HTTP request.</p>
+ *
+ * @param name the name of the request header to add.
+ * @param value the value of the request header to add.
+ * @param appendValue if the current value should be appended, or in other
+ * words, that two headers with the same can coexist.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name or value were <b>null</b>.
+ */
+ public HttpClient addRequestHeader(String name, String value,
+ boolean appendValue) {
+ final String key = name.toLowerCase();
+ try {
+ RequestHeader header;
+ if (appendValue) {
+ header = (RequestHeader) this.requestHeaders.get(key);
+ if (header == null) {
+ header = new RequestHeader(name);
+ this.requestHeaders.put(key, header);
+ }
+ } else {
+ header = new RequestHeader(name);
+ this.requestHeaders.put(key, header);
+ }
+ header.values.add((value + "\r\n").getBytes("ISO-8859-1"));
+ return this;
+ } catch (UnsupportedEncodingException exception) {
+ Error error = new InternalError("Standard encoding not supported");
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Remove the named header from the current HTTP request.</p>
+ *
+ * @param name the name of the request header to add.
+ * @return this {@link HttpClient} instance.
+ * @throws NullPointerException the name was <b>null</b>.
+ */
+ public HttpClient removeRequestHeader(String name) {
+ final String key = name.toLowerCase();
+ this.requestHeaders.remove(key);
+ return this;
+ }
+
+ /**
+ * <p>Remove all headers from the current HTTP request.</p>
+ *
+ * @return this {@link HttpClient} instance.
+ */
+ public HttpClient removeRequestHeaders() {
+ this.requestHeaders.clear();
+ return this;
+ }
+
+ /**
+ * <p>Return the first value for the specified response header.</p>
+ *
+ * @param name the name of the header whose value needs to be returned.
+ * @return a {@link String} or <b>null</b> if no such header exists.
+ */
+ public String getResponseHeader(String name) {
+ final String key = name.toLowerCase();
+ ResponseHeader header = (ResponseHeader) this.responseHeaders.get(key);
+ if (header == null) return null;
+ return (String) header.values.get(0);
+ }
+
+ /**
+ * <p>Return all the values for the specified response header.</p>
+ *
+ * @param name the name of the header whose values needs to be returned.
+ * @return a {@link List} or <b>null</b> if no such header exists.
+ */
+ public List getResponseHeaderValues(String name) {
+ final String key = name.toLowerCase();
+ ResponseHeader header = (ResponseHeader) this.responseHeaders.get(key);
+ if (header == null) return null;
+ return Collections.unmodifiableList(header.values);
+ }
+
+ /**
+ * <p>Return an {@link Iterator} over all response header names.</p>
+ *
+ * @return a <b>non-null</b> {@link Iterator}.
+ */
+ public Iterator getResponseHeaderNames() {
+ final Iterator iterator = this.responseHeaders.values().iterator();
+ return new Iterator() {
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+ public Object next() {
+ return ((ResponseHeader) iterator.next()).name;
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * <p>Return the protocol returned by the remote HTTP server.</p>
+ *
+ * @return a <b>non-null</b> {@link String} like <code>HTTP/1.0</code>.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public String getResponseProtocol() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.protocol;
+ }
+
+ /**
+ * <p>Return the status returned by the remote HTTP server.</p>
+ *
+ * @return a number representing the HTTP status of the response.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public int getResponseStatus() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.status;
+ }
+
+ /**
+ * <p>Return the status message returned by the remote HTTP server.</p>
+ *
+ * @return a <b>non-null</b> {@link String} like <code>OK</code>.
+ * @throws IllegalStateException if the connection was never connected.
+ */
+ public String getResponseMessage() {
+ if (this.status == null) throw new IllegalStateException();
+ return this.status.message;
+ }
+
+ /* ====================================================================== */
+ /* PRIVATE METHODS TO USE WHEN CONNECTING */
+ /* ====================================================================== */
+
+ /**
+ * <p>Read a single line of the HTTP response from the specified
+ * {@link InputStream} into a byte array (trailing CRLF are removed).</p>
+ */
+ private byte[] readLine(InputStream input)
+ throws IOException {
+ int x = 0;
+ while (true) {
+ int b = input.read();
+ if (b == -1) break;
+ if (b == 0x0A) break;
+ if (x == this.buffer.length) break;
+ this.buffer[x ++] = (byte) b;
+ }
+ if ((x > 0) && (this.buffer[x - 1] == 0x0D)) x--;
+ final byte array[] = new byte[x];
+ System.arraycopy(this.buffer, 0, array, 0, x);
+ return array;
+ }
+
+ /**
+ * <p>Read the status line from the specified {@link InputStream} and
+ * setup the {@link #status} field.</p>
+ */
+ private void readStatusLine(InputStream input)
+ throws IOException {
+ /* Prepare the different buffers required for parsing */
+ final byte line[] = this.readLine(input);
+ final byte buff[] = new byte[line.length];
+ final String comp[] = new String[3];
+ int lpos = 0;
+ int bpos = 0;
+ int cpos = 0;
+ boolean spc = true;
+
+ /* Iterate every single byte in the line, splitting up components */
+ while (lpos < line.length) {
+ final byte b = line[lpos ++];
+ if (spc) {
+ if ((b == 0x09) || (b == 0x20)) continue;
+ buff[bpos ++] = b;
+ if (cpos == 2) break;
+ else spc = false;
+ } else {
+ if ((b == 0x09) || (b == 0x20)) {
+ comp[cpos ++] = new String(buff, 0, bpos, "US-ASCII");
+ bpos = 0;
+ spc = true;
+ continue;
+ }
+ buff[bpos ++] = b;
+ }
+
+ }
+ /*
+ * Copy remaining bytes out of the line buffer and ensure all
+ * components in the status line are not null;
+ */
+ while (lpos < line.length) buff[bpos ++] = line[lpos++];
+ if (bpos > 0) comp[cpos++] = new String(buff, 0, bpos, "US-ASCII");
+ for (int x = cpos; x < 3; x++) comp[x] = "";
+
+ /* Create the status object */
+ this.status = new Status(comp[0], comp[1], comp[2]);
+ }
+
+ /**
+ * <p>Read all the response headers from the specified {@link InputStream}
+ * and setup the {@link #responseHeaders} field.</p>
+ */
+ private void readHeaders(InputStream input)
+ throws IOException {
+ /* Clear out any previous header */
+ this.responseHeaders.clear();
+
+ /* Process the input stream until we find an empty line */
+ while (true) {
+ final byte array[] = this.readLine(input);
+ if (array.length == 0) break;
+
+ /* Identify where the colon is in the header */
+ int pos = -1;
+ while (pos < array.length) if (array[++ pos] == 0x03A) break;
+ if (pos == 0) continue;
+ if (pos == array.length - 1) continue;
+
+ /* Prepare strings for name and value */
+ final int o = pos + 1;
+ final int l = array.length - o;
+ final String name = new String(array, 0, pos, "US-ASCII").trim();
+ final String value = new String(array, o, l, "ISO-8859-1").trim();
+ if ((name.length() == 0) || (value.length() == 0)) continue;
+
+ /* Store the header value in a list for now */
+ final String key = name.toLowerCase();
+ ResponseHeader hdr = (ResponseHeader) this.responseHeaders.get(key);
+ if (hdr == null) {
+ hdr = new ResponseHeader(name);
+ this.responseHeaders.put(key, hdr);
+ }
+ hdr.values.add(value);
+ }
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL CLASS REPRESENTNG THE STATUS LINE AND AN ENCODED HEADER */
+ /* ====================================================================== */
+
+ /**
+ * <p>A simple internal class representing a response status line.</p>
+ */
+ private static final class Status {
+
+ /** <p>The response protocol, like <code>HTTP/1.0</code> */
+ private final String protocol;
+ /** <p>The response status code, like <code>302</code> */
+ private final int status;
+ /** <p>The response message, like <code>Moved permanently</code> */
+ private final String message;
+
+ /**
+ * <p>Create a new {@link Status} verifying the supplied parameters.</p>
+ *
+ * @throws IOException if an error occurred verifying the parameters.
+ */
+ private Status(String protocol, String status, String message)
+ throws IOException {
+
+ /* Verify the protocol */
+ if ("HTTP/1.0".equals(protocol) || "HTTP/1.1".equals(protocol)) {
+ this.protocol = protocol;
+ } else {
+ throw new IOException("Unknown protocol \"" + protocol + "\"");
+ }
+
+ /* Verify the status */
+ try {
+ this.status = Integer.parseInt(status);
+ if ((this.status < 100) || (this.status > 599)) {
+ throw new IOException("Invalid status \"" + status + "\"");
+ }
+ } catch (RuntimeException exception) {
+ final String error = "Can't parse status \"" + status + "\"";
+ IOException throwable = new IOException(error);
+ throw (IOException) throwable.initCause(exception);
+ }
+
+ /* Decode the message */
+ if ("".equals(message)) this.message = "No message";
+ else this.message = EncodingTools.urlDecode(message, "ISO-8859-1");
+ }
+ }
+
+ /**
+ * <p>A simple internal class representing a request header.</p>
+ */
+ private static final class RequestHeader {
+
+ /** <p>The byte array of the header's name.</p> */
+ private final byte name[];
+ /** <p>A {@link List} of all the header's values.</p> */
+ private final List values;
+
+ /** <p>Create a new {@link RequestHeader} instance.</p> */
+ private RequestHeader(String name)
+ throws UnsupportedEncodingException {
+ this.name = (name + ": ").getBytes("US-ASCII");
+ this.values = new ArrayList();
+ }
+ }
+
+ /**
+ * <p>A simple internal class representing a response header.</p>
+ */
+ private static final class ResponseHeader {
+
+ /** <p>The real name of the response header.</p> */
+ private final String name;
+ /** <p>A {@link List} of all the header's values.</p> */
+ private final List values;
+
+ /** <p>Create a new {@link ResponseHeader} instance.</p> */
+ private ResponseHeader(String name)
+ throws UnsupportedEncodingException {
+ this.name = name;
+ this.values = new ArrayList();
+ }
+ }
+
+ /* ====================================================================== */
+ /* LIMITED STREAMS */
+ /* ====================================================================== */
+
+ /**
+ * <p>A simple {@link OutputStream} writing at most the number of bytes
+ * specified at construction.</p>
+ */
+ private static final class Output extends OutputStream {
+
+ /** <p>The {@link OutputStream} wrapped by this instance.</p> */
+ private final OutputStream output;
+ /** <p>The {@link InputStream} wrapped by this instance.</p> */
+ private final InputStream input;
+ /** <p>The {@link HttpClient} wrapped by this instance.</p> */
+ private final HttpClient client;
+ /** <p>The number of bytes yet to write.</p> */
+ private long remaining;
+ /** <p>A flag indicating whether this instance was closed.</p> */
+ private boolean closed;
+
+ /**
+ * <p>Create a new {@link Output} instance with the specified limit
+ * of bytes to write.</p>
+ *
+ * @param output the {@link OutputStream} to wrap.
+ * @param remainig the maximum number of bytes to write.
+ */
+ private Output(HttpClient client, InputStream input,
+ OutputStream output, long remaining) {
+ if (input == null) throw new NullPointerException();
+ if (output == null) throw new NullPointerException();
+ if (client == null) throw new NullPointerException();
+ this.remaining = remaining;
+ this.client = client;
+ this.output = output;
+ this.input = input;
+ }
+
+ public void write(byte buf[])
+ throws IOException {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len)
+ throws IOException {
+ if (len > this.remaining) {
+ throw new IOException("Too much data to write");
+ } else try {
+ this.output.write(buf, off, len);
+ } finally {
+ this.remaining -= len;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public void write(int b)
+ throws IOException {
+ if (this.remaining < 1) {
+ throw new IOException("Too much data to write");
+ } else try {
+ this.output.write(b);
+ } finally {
+ this.remaining -= 1;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public void flush()
+ throws IOException {
+ this.output.flush();
+ }
+
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ if (this.remaining > 0)
+ throw new IOException(this.remaining + " bytes left to write");
+ this.closed = true;
+ this.output.flush();
+
+ /* Read the status and headers from the connection and verify */
+ this.client.readStatusLine(this.input);
+ this.client.readHeaders(this.input);
+ this.client.verify();
+
+ /* Evaluate the content length specified by the server */
+ final String slen = this.client.getResponseHeader("Content-Length");
+ long blen = -1;
+ if (slen != null) try {
+ blen = Long.parseLong(slen);
+ } catch (NumberFormatException exception) {
+ /* Swallow this, be liberal in what we accept */
+ }
+
+ /* Return an output stream if the content length was not zero */
+ this.client.xoutput = null;
+ this.client.xinput = new Input(this.client, this.input, blen);
+ }
+
+ protected void finalize()
+ throws Throwable {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /**
+ * <p>A simple {@link InputStream} reading at most the number of bytes
+ * specified at construction.</p>
+ */
+ private static final class Input extends InputStream {
+
+ /** <p>The {@link InputStream} wrapped by this instance.</p> */
+ private final InputStream input;
+ /** <p>The {@link HttpClient} wrapped by this instance.</p> */
+ private final HttpClient client;
+ /** <p>The number of bytes yet to write or -1 if unknown.</p> */
+ private long remaining;
+ /** <p>A flag indicating whether this instance was closed.</p> */
+ private boolean closed;
+
+ /**
+ * <p>Create a new {@link Input} instance with the specified limit
+ * of bytes to read.</p>
+ *
+ * @param input the {@link InputStream} to wrap.
+ * @param remainig the maximum number of bytes to read or -1 if unknown.
+ */
+ private Input(HttpClient client, InputStream input, long remaining) {
+ if (input == null) throw new NullPointerException();
+ if (client == null) throw new NullPointerException();
+ this.remaining = remaining < 0 ? Long.MAX_VALUE : remaining;
+ this.client = client;
+ this.input = input;
+ }
+
+ public int read()
+ throws IOException {
+ if (this.remaining < 1) {
+ return -1;
+ } else try {
+ return this.input.read();
+ } finally {
+ this.remaining -= 1;
+ if (this.remaining < 1) this.close();
+ }
+ }
+
+ public int read(byte buf[])
+ throws IOException {
+ return read(buf, 0, buf.length);
+ }
+
+ public int read(byte buf[], int off, int len)
+ throws IOException {
+ if (this.remaining <= 0) return -1;
+ if (len > this.remaining) len = (int) this.remaining;
+ int count = 0;
+ try {
+ count = this.input.read(buf, off, len);
+ } finally {
+ this.remaining -= count;
+ if (this.remaining < 1) this.close();
+ }
+ return count;
+ }
+
+ public long skip(long n)
+ throws IOException {
+ if (this.remaining <= 0) return -1;
+ if (n > this.remaining) n = this.remaining;
+ long count = 0;
+ try {
+ count = this.input.skip(n);
+ } finally {
+ this.remaining -= count;
+ if (this.remaining < 1) this.close();
+ }
+ return count;
+ }
+
+ public int available()
+ throws IOException {
+ int count = this.input.available();
+ if (count < this.remaining) return count;
+ return (int) this.remaining;
+ }
+
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ this.closed = true;
+ try {
+ this.input.close();
+ } finally {
+ this.client.disconnect();
+ }
+ }
+
+ public void mark(int readlimit) {
+ this.input.mark(readlimit);
+ }
+
+ public void reset()
+ throws IOException {
+ this.input.reset();
+ }
+
+ public boolean markSupported() {
+ return this.input.markSupported();
+ }
+
+ protected void finalize()
+ throws Throwable {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /* ====================================================================== */
+ /* UTILITY FETCHER */
+ /* ====================================================================== */
+
+ /**
+ * <p><b>Utility method:</b> fetch the location specified on the command
+ * line following redirects if necessary.</p>
+ *
+ * <p>The final location fetched (in case of redirections it might change)
+ * will be reported on the {@link System#err system error stream} alongside
+ * with any errors encountered while processing.</p>
+ */
+ public static final void main(String args[]) {
+ try {
+ final HttpClient c = new HttpClient(args[0]).connect();
+ final InputStream i = c.getResponseStream();
+ for (int b = i.read(); b >= 0; b = i.read()) System.out.write(b);
+ c.disconnect();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace(System.err);
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java
new file mode 100644
index 000000000..fe0eba41b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java
@@ -0,0 +1,901 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.http;
+
+import it.could.util.StreamTools;
+import it.could.util.StringTools;
+import it.could.util.location.Location;
+import it.could.util.location.Path;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+/**
+ * <p>A class implementing an extremely simple WebDAV Level 1 client based on
+ * the {@link HttpClient}.</p>
+ *
+ * <p>Once opened this class will represent a WebDAV collection. Users of this
+ * class can then from an instance of this, deal with relative parent and
+ * children resources.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class WebDavClient {
+
+ /** <p>The WebDAV resource asociated with this instance.</p> */
+ private Resource resource;
+ /** <p>A map of children resources of this instance.</p> */
+ private Map children;
+
+ /**
+ * <p>Create a new {@link WebDavClient} instance opening the collection
+ * identified by the specified {@link Location}.</p>
+ *
+ * @param location the {@link Location} of the WebDAV collection to open.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * {@link Location} specified does not point to a
+ * WebDAV collection.
+ * @throws NullPointerException if the {@link Location} was <b>null</b>.
+ */
+ public WebDavClient(Location location)
+ throws NullPointerException, IOException {
+ if (location == null) throw new NullPointerException("Null location");
+ this.reload(location);
+ }
+
+ /* ====================================================================== */
+ /* ACTIONS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Refresh this {@link WebDavClient} instance re-connecting to the remote
+ * collection and re-reading its properties.</p>
+ *
+ * @return this {@link WebDavClient} instance.
+ */
+ public WebDavClient refresh()
+ throws IOException {
+ this.reload(this.resource.location);
+ return this;
+ }
+
+ /**
+ * <p>Fetch the contents of the specified child resource of the collection
+ * represented by this {@link WebDavClient} instance.</p>
+ *
+ * @see #isCollection(String)
+ * @return a <b>non-null</b> {@link InputStream}.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified represents a collection.
+ * @throws NullPointerException if the child was <b>null</b>.
+ */
+ public InputStream get(String child)
+ throws NullPointerException, IOException {
+ if (child == null) throw new NullPointerException("Null child");
+ if (! this.isCollection(child)) {
+ final Location location = this.getLocation(child);
+ final HttpClient client = new HttpClient(location);
+ client.setAcceptableStatus(200).connect("GET");
+ return client.getResponseStream();
+ }
+ throw new IOException("Child \"" + child + "\" is a collection");
+ }
+
+ /**
+ * <p>Delete the child resource (or collection) of the collection
+ * represented by this {@link WebDavClient} instance.</p>
+ *
+ * @return this {@link WebDavClient} instance.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified represents a collection.
+ * @throws NullPointerException if the child was <b>null</b>.
+ */
+ public WebDavClient delete(String child)
+ throws NullPointerException, IOException {
+ if (child == null) throw new NullPointerException("Null child");
+ final HttpClient client = new HttpClient(this.getLocation(child));
+ client.setAcceptableStatus(204).connect("DELETE").disconnect();
+ return this.refresh();
+ }
+
+ /**
+ * <p>Create a new collection as a child of the collection represented
+ * by this {@link WebDavClient} instance.</p>
+ *
+ * <p>In comparison to {@link #put(String)} and {@link #put(String, long)}
+ * this method will fail if the specified child already exist.</p>
+ *
+ * @see #hasChild(String)
+ * @return this {@link WebDavClient} instance.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified already exist.
+ * @throws NullPointerException if the child was <b>null</b>.
+ */
+ public WebDavClient mkcol(String child)
+ throws NullPointerException, IOException {
+ if (child == null) throw new NullPointerException("Null child");
+ if (this.hasChild(child))
+ throw new IOException("Child \"" + child + "\" already exists");
+ final Location location = this.resource.location.resolve(child);
+ final HttpClient client = new HttpClient(location);
+ client.setAcceptableStatus(201).connect("MKCOL").disconnect();
+ return this.refresh();
+ }
+
+ /**
+ * <p>Create a new (or update the contents of a) child of of the collection
+ * represented by this {@link WebDavClient} instance.</p>
+ *
+ * <p>This method will behave exactly like the {@link #put(String, long)}
+ * method, but the data written to the returned {@link OutputStream} will
+ * be <i>buffered in memory</i> and will be transmitted to the remote
+ * server only when the {@link OutputStream#close()} method is called.</p>
+ *
+ * <p>If the returned {@link OutputStream} is garbage collected before the
+ * {@link OutputStream#close() close()} method is called, the entire
+ * transaction will be aborted and no connection to the remote server will
+ * be established.</p>
+ *
+ * <p>Use this method in extreme cases. In normal circumstances always rely
+ * on the {@link #put(String, long)} method.</p>
+ *
+ * @see #put(String, long)
+ * @return a <b>non-null</b> {@link OutputStream} instance.
+ * @throws NullPointerException if the child was <b>null</b>.
+ */
+ public OutputStream put(final String child)
+ throws NullPointerException {
+ if (child == null) throw new NullPointerException("Null child");
+ final WebDavClient client = this;
+ return new ByteArrayOutputStream() {
+ private boolean closed = false;
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ this.flush();
+ OutputStream output = client.put(child, this.buf.length);
+ output.write(this.buf);
+ output.flush();
+ output.close();
+ }
+
+ protected void finalize()
+ throws Throwable {
+ this.closed = true;
+ super.finalize();
+ }
+ };
+ }
+
+ /**
+ * <p>Create a new (or update the contents of a) child of of the collection
+ * represented by this {@link WebDavClient} instance.</p>
+ *
+ * <p>If the specified child {@link #hasChild(String) already exists} on
+ * the remote server, it will be {@link #delete(String) deleted} before
+ * writing.</p>
+ *
+ * @return a <b>non-null</b> {@link OutputStream} instance.
+ * @throws NullPointerException if the child was <b>null</b>.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified already exist.
+ */
+ public OutputStream put(String child, long length)
+ throws NullPointerException, IOException {
+ if (child == null) throw new NullPointerException("Null child");
+ if (this.hasChild(child)) this.delete(child);
+ final Location location = this.resource.location.resolve(child);
+ final HttpClient client = new HttpClient(location);
+ client.setAcceptableStatuses(new int[] { 201, 204 });
+ client.connect("PUT", length);
+
+ final WebDavClient webdav = this;
+ return new BufferedOutputStream(client.getRequestStream()) {
+ boolean closed = false;
+ public void close()
+ throws IOException {
+ if (this.closed) return;
+ try {
+ super.close();
+ } finally {
+ this.closed = true;
+ webdav.refresh();
+ }
+ }
+ protected void finalize()
+ throws Throwable {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+ };
+ }
+
+ /**
+ * <p>Open the specified child collection of the collection represented by
+ * this {@link WebDavClient} as a new {@link WebDavClient} instance.</p>
+ *
+ * <p>If the specified child is &quot;<code>.</code>&quot; this method
+ * will behave exactly like {@link #refresh()} and <i>this instance</i>
+ * will be returned.</p>
+ *
+ * <p>If the specified child is &quot;<code>..</code>&quot; this method
+ * will behave exactly like {@link #parent()}.</p>
+ *
+ * @return a <b>non-null</b> {@link WebDavClient} instance.
+ * @throws NullPointerException if the child was <b>null</b>.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified did not exist.
+ */
+ public WebDavClient open(String child)
+ throws NullPointerException, IOException {
+ if (child == null) throw new NullPointerException("Null child");
+ if (".".equals(child)) return this.refresh();
+ if ("..".equals(child)) return this.parent();
+ if (resource.collection) {
+ Location loc = this.getLocation().resolve(this.getLocation(child));
+ return new WebDavClient(loc);
+ }
+ throw new IOException("Child \"" + child + "\" is not a collection");
+ }
+
+ /**
+ * <p>Open the parent collection of the collection represented by this
+ * {@link WebDavClient} as a new {@link WebDavClient} instance.</p>
+ *
+ * @return a <b>non-null</b> {@link WebDavClient} instance.
+ * @throws IOException if an I/O or network error occurred, or if the
+ * child specified did not exist.
+ */
+ public WebDavClient parent()
+ throws IOException {
+ final Location location = this.resource.location.resolve("..");
+ return new WebDavClient(location);
+ }
+
+ /* ====================================================================== */
+ /* ACCESSOR METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return an {@link Iterator} over {@link String}s for all the children
+ * of the collection represented by this {@link WebDavClient} instance.</p>
+ */
+ public Iterator iterator() {
+ return this.children.keySet().iterator();
+ }
+
+ /**
+ * <p>Checks if the collection represented by this {@link WebDavClient}
+ * contains the specified child.</p>
+ */
+ public boolean hasChild(String child) {
+ return this.children.containsKey(child);
+ }
+
+ /**
+ * <p>Return the {@link Location} associated with the collection
+ * represented by this {@link WebDavClient}.</p>
+ *
+ * <p>The returned {@link Location} can be different from the one specified
+ * at construction, in case the server redirected us upon connection.</p>
+ */
+ public Location getLocation() {
+ return this.resource.location;
+ }
+
+ /**
+ * <p>Return the content length (in bytes) of the collection represented
+ * by this {@link WebDavClient} as passed to us by the WebDAV server.</p>
+ */
+ public long getContentLength() {
+ return this.resource.contentLength;
+ }
+
+ /**
+ * <p>Return the content type (mime-type) of the collection represented
+ * by this {@link WebDavClient} as passed to us by the WebDAV server.</p>
+ */
+ public String getContentType() {
+ return this.resource.contentType;
+ }
+
+ /**
+ * <p>Return the last modified {@link Date} of the collection represented
+ * by this {@link WebDavClient} as passed to us by the WebDAV server.</p>
+ */
+ public Date getLastModified() {
+ return this.resource.lastModified;
+ }
+
+ /**
+ * <p>Return the creation {@link Date} of the collection represented
+ * by this {@link WebDavClient} as passed to us by the WebDAV server.</p>
+ */
+ public Date getCreationDate() {
+ return this.resource.creationDate;
+ }
+
+ /**
+ * <p>Return the {@link Location} associated with the specified child of
+ * the collection represented by this {@link WebDavClient}.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ public Location getLocation(String child)
+ throws IOException {
+ Location location = this.getResource(child).location;
+ return this.resource.location.resolve(location);
+ }
+
+ /**
+ * <p>Checks if the specified child of the collection represented by this
+ * {@link WebDavClient} instance is a collection.</p>
+ */
+ public boolean isCollection(String child)
+ throws IOException {
+ return this.getResource(child).collection;
+ }
+
+ /**
+ * <p>Return the content length (in bytes) associated with the specified
+ * child of the collection represented by this {@link WebDavClient}.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ public long getContentLength(String child)
+ throws IOException {
+ return this.getResource(child).contentLength;
+ }
+
+ /**
+ * <p>Return the content type (mime-type) associated with the specified
+ * child of the collection represented by this {@link WebDavClient}.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ public String getContentType(String child)
+ throws IOException {
+ return this.getResource(child).contentType;
+ }
+
+ /**
+ * <p>Return the last modified {@link Date} associated with the specified
+ * child of the collection represented by this {@link WebDavClient}.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ public Date getLastModified(String child)
+ throws IOException {
+ return this.getResource(child).lastModified;
+ }
+
+ /**
+ * <p>Return the creation {@link Date} associated with the specified
+ * child of the collection represented by this {@link WebDavClient}.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ public Date getCreationDate(String child)
+ throws IOException {
+ return this.getResource(child).creationDate;
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the resource associated with the specified child.</p>
+ *
+ * @throws IOException if the specified child does not exist.
+ * @throws NullPointerException if the specified child was <b>null</b>.
+ */
+ private Resource getResource(String child)
+ throws IOException {
+ if (child == null) throw new NullPointerException();
+ final Resource resource = (Resource) this.children.get(child);
+ if (resource == null) throw new IOException("Not found: " + child);
+ return resource;
+ }
+
+ /**
+ * <p>Contact the remote WebDAV server and fetch all properties.</p>
+ */
+ private void reload(Location location)
+ throws IOException {
+
+ /* Do an OPTIONS over onto the location */
+ location = this.options(location);
+
+ /* Do a PROPFIND to figure out the properties and the children */
+ final Iterator iterator = this.propfind(location).iterator();
+ final Map children = new HashMap();
+ while (iterator.hasNext()) {
+ final Resource resource = (Resource) iterator.next();
+ final Path path = resource.location.getPath();
+ if (path.size() == 0) {
+ resource.location = location.resolve(resource.location);
+ this.resource = resource;
+ } else if (path.size() == 1) {
+ final Path.Element element = (Path.Element) path.get(0);
+ if ("..".equals(element.getName())) continue;
+ children.put(element.toString(), resource);
+ }
+ }
+
+ /* Check if the current resource was discovered */
+ if (this.resource == null)
+ throw new IOException("Current resource not returned in PROOPFIND");
+
+ /* Don't actually allow resources to be modified */
+ this.children = Collections.unmodifiableMap(children);
+ }
+
+ /**
+ * <p>Contact the remote WebDAV server and do an OPTIONS lookup.</p>
+ */
+ private Location options(Location location)
+ throws IOException {
+ /* Create the new HttpClient instance associated with the location */
+ final HttpClient client = new HttpClient(location);
+ client.setAcceptableStatus(200).connect("OPTIONS", true).disconnect();
+
+ /* Check that the remote server returned the "Dav" header */
+ final List davHeader = client.getResponseHeaderValues("dav");
+ if (davHeader == null) {
+ throw new IOException("Server did not respond with a DAV header");
+ }
+
+ /* Check if the OPTIONS request contained the DAV header */
+ final Iterator iterator = davHeader.iterator();
+ boolean foundLevel1 = false;
+ while (iterator.hasNext() && (! foundLevel1)) {
+ String value = (String) iterator.next();
+ StringTokenizer tokenizer = new StringTokenizer(value, ",");
+ while (tokenizer.hasMoreTokens()) {
+ if (! "1".equals(tokenizer.nextToken().trim())) continue;
+ foundLevel1 = true;
+ break;
+ }
+ }
+
+ /* Return the (possibly redirected) location or fail miserably */
+ if (foundLevel1) return client.getLocation();
+ throw new IOException("Server doesn't support DAV Level 1");
+ }
+
+ /**
+ * <p>Contact the remote WebDAV server and do a PROPFIND lookup, returning
+ * a {@link List} of all scavenged resources.</p>
+ */
+ private List propfind(Location location)
+ throws IOException {
+ /* Create the new HttpClient instance associated with the location */
+ final HttpClient client = new HttpClient(location);
+ client.addRequestHeader("Depth", "1");
+ client.setAcceptableStatus(207).connect("PROPFIND", true);
+
+ /* Get the XML SAX Parser and parse the output of the PROPFIND */
+ try {
+ final SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ final SAXParser parser = factory.newSAXParser();
+ final String systemId = location.toString();
+ final InputSource source = new InputSource(systemId);
+ final Handler handler = new Handler(location);
+ source.setByteStream(client.getResponseStream());
+ parser.parse(source, handler);
+ return handler.list;
+
+ } catch (ParserConfigurationException exception) {
+ Exception throwable = new IOException("Error creating XML parser");
+ throw (IOException) throwable.initCause(exception);
+ } catch (SAXException exception) {
+ Exception throwable = new IOException("Error creating XML parser");
+ throw (IOException) throwable.initCause(exception);
+ } finally {
+ client.disconnect();
+ }
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL CLASSES */
+ /* ====================================================================== */
+
+ /**
+ * <p>An internal XML {@link DefaultHandler} used to parse out the various
+ * details of a PROPFIND response.</p>
+ */
+ private static final class Handler extends DefaultHandler {
+
+ /* ================================================================== */
+ /* PSEUDO-XPATH LOCATIONS FOR QUICK-AND-DIRTY LOCATION LOOKUP */
+ /* ================================================================== */
+ private static final String RESPONSE_PATH = "/multistatus/response";
+ private static final String HREF_PATH = "/multistatus/response/href";
+ private static final String COLLECTION_PATH =
+ "/multistatus/response/propstat/prop/resourcetype/collection";
+ private static final String GETCONTENTTYPE_PATH =
+ "/multistatus/response/propstat/prop/getcontenttype";
+ private static final String GETLASTMODIFIED_PATH =
+ "/multistatus/response/propstat/prop/getlastmodified";
+ private static final String GETCONTENTLENGTH_PATH =
+ "/multistatus/response/propstat/prop/getcontentlength";
+ private static final String CREATIONDATE_PATH =
+ "/multistatus/response/propstat/prop/creationdate";
+
+ /** <p>The {@link Location} for resolving all other links.</p> */
+ private final Location base;
+ /** <p>The {@link List} of all scavenged resources.</p> */
+ private final List list = new ArrayList();
+ /** <p>The resource currently being processed.</p> */
+ private Resource rsrc = null;
+ /** <p>A {@link StringBuffer} holding character data.</p> */
+ private StringBuffer buff = null;
+ /** <p>A {@link Stack} for quick-and-dirty pseudo XPath lookups.</p> */
+ private Stack stack = new Stack();
+
+ /**
+ * <p>Create a new instance specifying the base {@link Location}.</p>
+ */
+ private Handler(Location location) {
+ this.base = location;
+ }
+
+ /**
+ * <p>Push an element name in the stack for pseudo-XPath lookups.</p>
+ *
+ * @return a {@link String} like <code>/element/element/element</code>.
+ */
+ private String pushPath(String path) {
+ this.stack.push(path.toLowerCase());
+ final StringBuffer buffer = new StringBuffer();
+ for (int x = 0; x < this.stack.size(); x ++)
+ buffer.append('/').append(this.stack.get(x));
+ return buffer.toString();
+ }
+
+ /**
+ * <p>Pop the last element name from the pseudo-XPath lookup stack.</p>
+ *
+ * @return a {@link String} like <code>/element/element/element</code>.
+ */
+ private String popPath(String path)
+ throws SAXException {
+ final StringBuffer buffer = new StringBuffer();
+ final String last = (String) this.stack.pop();
+ if (path.toLowerCase().equals(last)) {
+ for (int x = 0; x < this.stack.size(); x ++)
+ buffer.append('/').append(this.stack.get(x));
+ return buffer.append('/').append(last).toString();
+ }
+ throw new SAXException("Tag <" + path + "/> unbalanced at path \""
+ + pushPath(last) + "\"");
+ }
+
+ /**
+ * <p>Handle the start-of-element SAX event.</p>
+ */
+ public void startElement(String uri, String l, String q, Attributes a)
+ throws SAXException {
+ if (! "DAV:".equals(uri.toUpperCase())) return;
+ final String path = this.pushPath(l);
+
+ if (RESPONSE_PATH.equals(path)) {
+ this.rsrc = new Resource();
+
+ } else if (COLLECTION_PATH.equals(path)) {
+ if (this.rsrc != null) this.rsrc.collection = true;
+
+ } else if (GETCONTENTTYPE_PATH.equals(path) ||
+ GETLASTMODIFIED_PATH.equals(path) ||
+ GETCONTENTLENGTH_PATH.equals(path) ||
+ CREATIONDATE_PATH.equals(path) ||
+ HREF_PATH.equals(path)) {
+ this.buff = new StringBuffer();
+ }
+ }
+
+ /**
+ * <p>Handle the end-of-element SAX event.</p>
+ */
+ public void endElement(String uri, String l, String q)
+ throws SAXException {
+ if (! "DAV:".equals(uri.toUpperCase())) return;
+ final String path = this.popPath(l);
+ final String data = this.resetBuffer();
+
+ if (RESPONSE_PATH.equals(path)) {
+ if (this.rsrc != null) {
+ if (this.rsrc.location != null) {
+ if (this.rsrc.location.isAbsolute()) {
+ final String z = this.rsrc.location.toString();
+ throw new SAXException("Unresolved location " + z);
+ } else {
+ this.list.add(this.rsrc);
+ }
+ } else {
+ throw new SAXException("Null location for resource");
+ }
+ }
+
+ } else if (HREF_PATH.equals(path)) {
+ if (this.rsrc != null) try {
+ final Location resolved = this.base.resolve(data);
+ this.rsrc.location = this.base.relativize(resolved);
+ if (! this.rsrc.location.isRelative())
+ throw new SAXException("Unable to relativize location "
+ + this.rsrc.location);
+ } catch (MalformedURLException exception) {
+ final String msg = "Unable to resolve URL \"" + data + "\"";
+ SAXException throwable = new SAXException(msg, exception);
+ throw (SAXException) throwable.initCause(exception);
+ }
+
+ } else if (CREATIONDATE_PATH.equals(path)) {
+ if (this.rsrc != null)
+ this.rsrc.creationDate = StringTools.parseIsoDate(data);
+
+ } else if (GETCONTENTTYPE_PATH.equals(path)) {
+ if (this.rsrc != null) this.rsrc.contentType = data;
+
+ } else if (GETLASTMODIFIED_PATH.equals(path)) {
+ if (this.rsrc != null)
+ this.rsrc.lastModified = StringTools.parseHttpDate(data);
+
+ } else if (GETCONTENTLENGTH_PATH.equals(path)) {
+ if (this.rsrc != null) {
+ Long length = StringTools.parseNumber(data);
+ if (length != null) {
+ this.rsrc.contentLength = length.longValue();
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>Handle SAX characters notification.</p>
+ */
+ public void characters(char buffer[], int offset, int length) {
+ if (this.buff != null) this.buff.append(buffer, offset, length);
+ }
+
+ /**
+ * <p>Reset the current characters buffer and return it as a
+ * {@link String}.</p>
+ */
+ private String resetBuffer() {
+ if (this.buff == null) return null;
+ if (this.buff.length() == 0) {
+ this.buff = null;
+ return null;
+ }
+ final String value = this.buff.toString();
+ this.buff = null;
+ return value;
+ }
+ }
+
+ /**
+ * <p>A simple class holding the core resource properties.</p>
+ */
+ private static class Resource {
+ private Location location = null;
+ private boolean collection = false;
+ private long contentLength = -1;
+ private String contentType = null;
+ private Date lastModified = null;
+ private Date creationDate = null;
+ }
+
+ /* ====================================================================== */
+ /* COMMAND LINE CLIENT */
+ /* ====================================================================== */
+
+ /**
+ * <p>A command-line interface to a WebDAV repository.</p>
+ *
+ * <p>When invoked from the command line, this class requires one only
+ * argument, the URL location of the WebDAV repository to connect to.</p>
+ *
+ * <p>After connection this method will interact with the user using an
+ * extremely simple console-based interface.</p>
+ */
+ public static void main(String args[])
+ throws IOException {
+ final InputStreamReader r = new InputStreamReader(System.in);
+ final BufferedReader in = new BufferedReader(r);
+ WebDavClient client = new WebDavClient(Location.parse(args[0]));
+
+ while (true) try {
+ System.out.print("[" + client.getLocation() + "] -> ");
+ args = parse(in.readLine());
+ if (args == null) break;
+ if (args[0].equals("list")) {
+ if (args[1] == null) list(client, System.out);
+ else list(client.open(args[1]), System.out);
+
+ } else if (args[0].equals("refresh")) {
+ client = client.refresh();
+
+ } else if (args[0].equals("get")) {
+ if (args[1] != null) {
+ final InputStream input = client.get(args[1]);
+ final File file = new File(args[2]).getCanonicalFile();
+ final OutputStream output = new FileOutputStream(file);
+ final long bytes = StreamTools.copy(input, output);
+ System.out.println("Fetched child \"" + args[1] +
+ "\" to file \"" + file + "\" (" +
+ bytes + " bytes)");
+ }
+ else System.out.print("Can't \"get\" null");
+
+ } else if (args[0].equals("put")) {
+ if (args[1] != null) {
+ final File file = new File(args[1]).getCanonicalFile();
+ final InputStream input = new FileInputStream(file);
+ final OutputStream output = client.put(args[2], file.length());
+ final long bytes = StreamTools.copy(input, output);
+ System.out.println("Uploaded file \"" + file +
+ "\" to child \"" + args[2] + "\" (" +
+ bytes + " bytes)");
+ }
+ else System.out.print("Can't \"put\" null");
+
+ } else if (args[0].equals("mkcol")) {
+ if (args[1] != null) {
+ client.mkcol(args[1]);
+ System.out.println("Created \"" + args[1] + "\"");
+ }
+ else System.out.print("Can't \"mkcol\" null");
+
+ } else if (args[0].equals("delete")) {
+ if (args[1] != null) {
+ client.delete(args[1]);
+ System.out.println("Deleted \"" + args[1] + "\"");
+ }
+ else System.out.print("Can't \"delete\" null");
+
+ } else if (args[0].equals("cd")) {
+ if (args[1] != null) client = client.open(args[1]);
+ else System.out.print("Can't \"cd\" to null");
+
+ } else if (args[0].equals("quit")) {
+ break;
+
+ } else {
+ System.out.print("Invalid command \"" + args[0] + "\". ");
+ System.out.println("Valid commands are:");
+ System.out.println(" - \"list\" list the children child");
+ System.out.println(" - \"get\" fetch the specified child");
+ System.out.println(" - \"put\" put the specified child");
+ System.out.println(" - \"mkcol\" create a collection");
+ System.out.println(" - \"delete\" delete a child");
+ System.out.println(" - \"put\" put the specified resource");
+ System.out.println(" - \"cd\" change the location");
+ System.out.println(" - \"refresh\" refresh this location");
+ System.out.println(" - \"quit\" quit this application");
+ }
+ } catch (Exception exception) {
+ exception.printStackTrace(System.err);
+ }
+ System.err.println();
+ }
+
+ /**
+ * <p>Parse a line entered by the user returning a three-tokens argument
+ * list (command, argument 1, argument 2)</p>
+ */
+ private static String[] parse(String line) {
+ if (line == null) return null;
+ final String array[] = new String[3];
+ final StringTokenizer tokenizer = new StringTokenizer(line);
+ int offset = 0;
+ while (tokenizer.hasMoreTokens() && (offset < 3))
+ array[offset ++] = tokenizer.nextToken();
+ if (array[0] == null) return null;
+ if (array[2] == null) array[2] = array[1];
+ return array;
+ }
+
+ /**
+ * <p>Pseudo-nicely display a list of the children of a collection</p>
+ */
+ private static void list(WebDavClient client, PrintStream out)
+ throws IOException {
+ out.print("C | ");
+ out.print("CONTENT TYPE | ");
+ out.print("CREATED | ");
+ out.print("MODIFIED | ");
+ out.print("SIZE | ");
+ out.println("NAME ");
+ for (Iterator iterator = client.iterator(); iterator.hasNext() ; ) {
+ final StringBuffer buffer = new StringBuffer();
+ String child = (String) iterator.next();
+ if (client.isCollection(child)) buffer.append("* | ");
+ else buffer.append(" | ");
+ format(buffer, client.getContentType(child), 15).append(" | ");
+ format(buffer, client.getCreationDate(child), 19).append(" | ");
+ format(buffer, client.getLastModified(child), 19).append(" | ");
+ format(buffer, client.getContentLength(child), 10).append(" | ");
+ out.println(buffer.append(child));
+ }
+ }
+
+ /** <p>Format a number aligning it to the right of a string.</p> */
+ private static StringBuffer format(StringBuffer buf, long num, int len) {
+ final String data;
+ if (num < 0) data = "";
+ else data = Long.toString(num);
+ final int spaces = len - data.length();
+ for (int x = 0; x < spaces; x++) buf.append(' ');
+ buf.append(data);
+ return buf;
+ }
+
+ /** <p>Format a string into an exact number of characters.</p> */
+ private static StringBuffer format(StringBuffer buf, Object obj, int len) {
+ final String string;
+ if (obj == null) {
+ string = ("[null]");
+ } else if (obj instanceof Date) {
+ SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ string = f.format((Date) obj);
+ } else {
+ string = obj.toString();
+ }
+ final StringBuffer buffer = new StringBuffer(string);
+ for (int x = string.length(); x < len; x ++) buffer.append(' ');
+ return buf.append(buffer.substring(0, len));
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html
new file mode 100644
index 000000000..9ca0cb4fa
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>HTTP Utilities</title>
+ </head>
+ <body>
+ <p>
+ This package contains a number of utility classes to access
+ <a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a> and
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a> servers.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java
new file mode 100644
index 000000000..24964795c
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java
@@ -0,0 +1,805 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.location;
+
+import it.could.util.StringTools;
+import it.could.util.encoding.Encodable;
+import it.could.util.encoding.EncodingTools;
+
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * <p>An utility class representing an HTTP-like URL.</p>
+ *
+ * <p>This class can be used to represent any URL that roughly uses the HTTP
+ * format. Compared to the standard {@link java.net.URL} class, the scheme part
+ * of the a {@link Location} is never checked, and it's up to the application
+ * to verify its correctness, while compared to the {@link java.net.URI} class,
+ * its parsing mechanism is a lot more relaxed (be liberal in what you accept,
+ * be strict in what you send).</p>
+ *
+ * <p>For a bigger picture on how this class works, this is an easy-to-read
+ * representation of what the different parts of a {@link Location} are:</p>
+ *
+ * <div align="center">
+ * <a href="url.pdf" target="_new" title="PDF Version">
+ * <img src="url.gif" alt="URL components" border="0">
+ * </a>
+ * </div>
+ *
+ * <p>One important difference between this implementation and the description
+ * of <a href="http://www.ietf.org/rfc/rfc1738.txt">URLs</a> and
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">URIs</a> is that parameter
+ * paths are represented <i>only at the end of the entire path structure</i>
+ * rather than for each path element. This over-simplification allows easy
+ * relativization of {@link Location}s when used with servlet containers, which
+ * normally use path parameters to encode the session id.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class Location implements Encodable {
+
+ /** <p>A {@link Map} of schemes and their default port number.</p> */
+ private static final Map schemePorts = new HashMap();
+ static {
+ schemePorts.put("acap", new Integer( 674));
+ schemePorts.put("dav", new Integer( 80));
+ schemePorts.put("ftp", new Integer( 21));
+ schemePorts.put("gopher", new Integer( 70));
+ schemePorts.put("http", new Integer( 80));
+ schemePorts.put("https", new Integer( 443));
+ schemePorts.put("imap", new Integer( 143));
+ schemePorts.put("ldap", new Integer( 389));
+ schemePorts.put("mailto", new Integer( 25));
+ schemePorts.put("news", new Integer( 119));
+ schemePorts.put("nntp", new Integer( 119));
+ schemePorts.put("pop", new Integer( 110));
+ schemePorts.put("rtsp", new Integer( 554));
+ schemePorts.put("sip", new Integer(5060));
+ schemePorts.put("sips", new Integer(5061));
+ schemePorts.put("snmp", new Integer( 161));
+ schemePorts.put("telnet", new Integer( 23));
+ schemePorts.put("tftp", new Integer( 69));
+ }
+
+ /** <p>The {@link List} of schemes of this {@link Location}.</p> */
+ private final Schemes schemes;
+ /** <p>The {@link Authority} of this {@link Location}.</p> */
+ private final Authority authority;
+ /** <p>The {@link Path} of this {@link Location}.</p> */
+ private final Path path;
+ /** <p>The {@link Parameters} of this {@link Location}.</p> */
+ private final Parameters parameters;
+ /** <p>The fragment part of this {@link Location}.</p> */
+ private final String fragment;
+ /** <p>The string representation of this {@link Location}.</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Location} instance.</p>
+ */
+ public Location(Schemes schemes, Authority authority, Path path,
+ Parameters parameters, String fragment)
+ throws MalformedURLException {
+ if ((schemes == null) && (authority != null))
+ throw new MalformedURLException("No schemes specified");
+ if ((schemes != null) && (authority == null))
+ throw new MalformedURLException("No authority specified");
+ if (path == null) throw new MalformedURLException("No path specified");
+
+ this.schemes = schemes;
+ this.authority = authority;
+ this.path = path;
+ this.parameters = parameters;
+ this.fragment = fragment;
+ this.string = EncodingTools.toString(this);
+ }
+
+ /* ====================================================================== */
+ /* STATIC CONSTRUCTION METHODS */
+ /* ====================================================================== */
+
+ public static Location parse(String url)
+ throws MalformedURLException {
+ try {
+ return parse(url, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ public static Location parse(String url, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ if (url == null) return null;;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ final String components[] = parseComponents(url);
+ final Schemes schemes = parseSchemes(components[0], encoding);
+ final int port = findPort(schemes, encoding);
+ final Authority auth = parseAuthority(components[1], port, encoding);
+ final Path path = Path.parse(components[2], encoding);
+ final Parameters params = Parameters.parse(components[3], '&', encoding);
+ final String fragment = components[4];
+ return new Location(schemes, auth, path, params, fragment);
+ }
+
+ /* ====================================================================== */
+ /* ACCESSOR METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return an unmodifiable {@link Schemes list of all schemes} for this
+ * {@link Location} instance or <b>null</b>.</p>
+ */
+ public Schemes getSchemes() {
+ return this.schemes;
+ }
+
+ /**
+ * <p>Return the {@link Location.Authority Authority} part for this
+ * {@link Location} or <b>null</b>.</p>
+ */
+ public Authority getAuthority() {
+ return this.authority;
+ }
+
+ /**
+ * <p>Return the <b>non-null</b> {@link Path Path} structure
+ * associated with this {@link Location} instance.</p>
+ */
+ public Path getPath() {
+ return this.path;
+ }
+
+ /**
+ * <p>Return an unmodifiable {@link Parameters list of all parameters}
+ * parsed from this {@link Location}'s query string or <b>null</b>.</p>
+ */
+ public Parameters getParameters() {
+ return this.parameters;
+ }
+
+ /**
+ * <p>Return the fragment of this {@link Location} unencoded.</p>
+ */
+ public String getFragment() {
+ return this.fragment;
+ }
+
+ /* ====================================================================== */
+ /* OBJECT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this instance.</p>
+ *
+ * <p>The specified {@link Object} must be a <b>non-null</b>
+ * {@link Location} instance whose {@link #toString() string value} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Location)) {
+ return this.string.equals(((Location)object).string);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * <p>Return the hash code value for this {@link Location} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Return the {@link String} representation of this {@link Location}
+ * instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the {@link String} representation of this {@link Location}
+ * instance using the specified character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ final StringBuffer buffer = new StringBuffer();
+
+ /* Render the schemes */
+ if (this.schemes != null)
+ buffer.append(this.schemes.toString(encoding)).append("://");
+
+ /* Render the authority part */
+ if (this.authority != null)
+ buffer.append(this.authority.toString(encoding));
+
+ /* Render the paths */
+ buffer.append(this.path.toString(encoding));
+
+ /* Render the query string */
+ if (this.parameters != null)
+ buffer.append('?').append(this.parameters.toString(encoding));
+
+ /* Render the fragment */
+ if (this.fragment != null) {
+ buffer.append('#');
+ buffer.append(EncodingTools.urlEncode(this.fragment, encoding));
+ }
+
+ /* Return the string */
+ return buffer.toString();
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Checks whether this {@link Location} is absolute or not.</p>
+ *
+ * <p>This method must not be confused with the similarly named
+ * {@link Path#isAbsolute() Path.isAbsolute()} method.
+ * This method will check whether the full {@link Location} is absolute (it
+ * has a scheme), while the one exposed by the {@link Path Path}
+ * class will check if the path is absolute.</p>
+ */
+ public boolean isAbsolute() {
+ return this.schemes != null && this.authority != null;
+ }
+
+ public boolean isRelative() {
+ return ! (this.isAbsolute() || this.path.isAbsolute());
+ }
+
+ public boolean isAuthoritative(Location location) {
+ if (! this.isAbsolute()) return false;
+ if (! location.isAbsolute()) return true;
+ return this.schemes.equals(location.schemes) &&
+ this.authority.equals(location.authority);
+ }
+
+ /* ====================================================================== */
+ /* RESOLUTION METHODS */
+ /* ====================================================================== */
+
+ public Location resolve(String url)
+ throws MalformedURLException {
+ try {
+ return this.resolve(parse(url, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ public Location resolve(String url, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return this.resolve(parse(url, encoding));
+ }
+
+ public Location resolve(Location location) {
+ if (! this.isAuthoritative(location)) return location;
+
+ /* Schemes are the same */
+ final Schemes schemes = this.schemes;
+
+ /* Authority needs to be merged (for username and password) */
+ final Authority auth;
+ if (location.authority != null) {
+ final String username = location.authority.username != null ?
+ location.authority.username :
+ this.authority.username;
+ final String password = location.authority.password != null ?
+ location.authority.password :
+ this.authority.password;
+ final String host = location.authority.host;
+ final int port = location.authority.port;
+ auth = new Authority(username, password, host, port);
+ } else {
+ auth = this.authority;
+ }
+
+ /* Path can be resolved */
+ final Path path = this.path.resolve(location.path);
+
+ /* Parametrs and fragment are the ones of the target */
+ final Parameters params = location.parameters;
+ final String fragment = location.fragment;
+
+ /* Create a new {@link Location} instance */
+ try {
+ return new Location(schemes, auth, path, params, fragment);
+ } catch (MalformedURLException exception) {
+ /* Should really never happen */
+ Error error = new InternalError("Can't instantiate Location");
+ throw (Error) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* RELATIVIZATION METHODS */
+ /* ====================================================================== */
+
+ public Location relativize(String url)
+ throws MalformedURLException {
+ try {
+ return this.relativize(parse(url, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ public Location relativize(String url, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return this.relativize(parse(url, encoding));
+ }
+
+ public Location relativize(Location location) {
+ final Path path;
+ if (!location.isAbsolute()) {
+ /* Target location is not absolute, its path might */
+ path = this.path.relativize(location.path);
+ } else {
+ if (this.isAuthoritative(location)) {
+ /* Target location is not on the same authority, process path */
+ path = this.path.relativize(location.path);
+ } else {
+ /* Not authoritative for a non-relative location, yah! */
+ return location;
+ }
+ }
+ try {
+ return new Location(null, null, path, location.parameters,
+ location.fragment);
+ } catch (MalformedURLException exception) {
+ /* Should really never happen */
+ Error error = new InternalError("Can't instantiate Location");
+ throw (Error) error.initCause(exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* INTERNAL PARSING ROUTINES */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the port number associated with the specified schemes.</p>
+ */
+ public static int findPort(List schemes, String encoding)
+ throws UnsupportedEncodingException {
+ if (schemes == null) return -1;
+ if (schemes.size() < 1) return -1;
+ Integer p = (Integer) schemePorts.get(schemes.get(schemes.size() - 1));
+ return p == null ? -1 : p.intValue();
+ }
+
+ /**
+ * <p>Parse <code>scheme://authority/path?query#fragment</code>.</p>
+ *
+ * @return an array of five {@link String}s: scheme (0), authority (1),
+ * path (2), query (3) and fragment (4).
+ */
+ private static String[] parseComponents(String url)
+ throws MalformedURLException {
+ /* Scheme, easy and simple */
+ final String scheme;
+ final String afterScheme;
+ final int schemeEnd = url.indexOf(":/");
+ if (schemeEnd > 0) {
+ scheme = url.substring(0, schemeEnd).toLowerCase();
+ afterScheme = url.substring(schemeEnd + 2);
+ } else if (schemeEnd == 0) {
+ throw new MalformedURLException("Missing scheme");
+ } else {
+ scheme = null;
+ afterScheme = url;
+ }
+
+ /* Authority (can be tricky because it can be emtpy) */
+ final String auth;
+ final String afterAuth;
+ if (scheme == null) {
+ // --> /path... or path...
+ afterAuth = afterScheme;
+ auth = null;
+ } else if (afterScheme.length() > 0 && afterScheme.charAt(0) == '/') {
+ // --> scheme://...
+ final int pathStart = afterScheme.indexOf('/', 1);
+ if (pathStart == 1) {
+ // --> scheme:///path...
+ afterAuth = afterScheme.substring(pathStart);
+ auth = null;
+ } else if (pathStart > 1) {
+ // --> scheme://authority/path...
+ afterAuth = afterScheme.substring(pathStart);
+ auth = afterScheme.substring(1, pathStart);
+ } else {
+ // --> scheme://authority (but no slashes for the path)
+ final int authEnds = StringTools.findFirst(afterScheme, "?#");
+ if (authEnds < 0) {
+ // --> scheme://authority (that's it, return)
+ auth = afterScheme.substring(1);
+ return new String[] { scheme, auth, "/", null, null };
+ }
+ // --> scheme://authority?... or scheme://authority#...
+ auth = afterScheme.substring(1, authEnds);
+ afterAuth = "/" + afterScheme.substring(authEnds);
+ }
+ } else {
+ // --> scheme:/path...
+ afterAuth = url.substring(schemeEnd + 1);
+ auth = null;
+ }
+
+ /* Path, can be terminated by '?' or '#' whichever is first */
+ final int pathEnds = StringTools.findFirst(afterAuth, "?#");
+ if (pathEnds < 0) {
+ // --> ...path... (no fragment or query, return now)
+ return new String[] { scheme, auth, afterAuth, null, null };
+ }
+
+ /* We have either a query, a fragment or both after the path */
+ final String path = afterAuth.substring(0, pathEnds);
+ final String afterPath = afterAuth.substring(pathEnds + 1);
+
+ /* Query? The query can contain a "#" and has an extra fragment */
+ if (afterAuth.charAt(pathEnds) == '?') {
+ final int fragmPos = afterPath.indexOf('#');
+ if (fragmPos < 0) {
+ // --> ...path...?... (no fragment)
+ return new String[] { scheme, auth, path, afterPath, null };
+ }
+
+ // --> ...path...?...#... (has also a fragment)
+ final String query = afterPath.substring(1, fragmPos);
+ final String fragm = afterPath.substring(fragmPos + 1);
+ return new String[] { scheme, auth, path, query, fragm };
+ }
+
+ // --> ...path...#... (a path followed by a fragment but no query)
+ return new String[] { scheme, auth, path, null, afterPath };
+ }
+
+ /**
+ * <p>Parse <code>scheme:scheme:scheme...</code>.</p>
+ */
+ private static Schemes parseSchemes(String scheme, String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ if (scheme == null) return null;
+ final String split[] = StringTools.splitAll(scheme, ':');
+ List list = new ArrayList();
+ for (int x = 0; x < split.length; x++) {
+ if (split[x] == null) continue;
+ list.add(EncodingTools.urlDecode(split[x], encoding));
+ }
+ if (list.size() != 0) return new Schemes(list);
+ throw new MalformedURLException("Empty scheme detected");
+ }
+
+ /**
+ * <p>Parse <code>username:password@hostname:port</code>.</p>
+ */
+ private static Authority parseAuthority(String auth, int defaultPort,
+ String encoding)
+ throws MalformedURLException, UnsupportedEncodingException {
+ if (auth == null) return null;
+ final String split[] = StringTools.splitOnce(auth, '@', true);
+ final String uinfo[] = StringTools.splitOnce(split[0], ':', false);
+ final String hinfo[] = StringTools.splitOnce(split[1], ':', false);
+ final int port;
+
+ if ((split[0] != null) && (split[1] == null))
+ throw new MalformedURLException("Missing required host info part");
+ if ((uinfo[0] == null) && (uinfo[1] != null))
+ throw new MalformedURLException("Password specified without user");
+ if ((hinfo[0] == null) && (hinfo[1] != null))
+ throw new MalformedURLException("Port specified without host");
+ try {
+ if (hinfo[1] != null) {
+ final int parsedPort = Integer.parseInt(hinfo[1]);
+ if ((parsedPort < 1) || (parsedPort > 65535)) {
+ final String message = "Invalid port number " + parsedPort;
+ throw new MalformedURLException(message);
+ }
+ /* If the specified port is the default one, ignore it! */
+ if (defaultPort == parsedPort) port = -1;
+ else port = parsedPort;
+ } else {
+ port = -1;
+ }
+ } catch (NumberFormatException exception) {
+ throw new MalformedURLException("Specified port is not a number");
+ }
+ return new Authority(EncodingTools.urlDecode(uinfo[0], encoding),
+ EncodingTools.urlDecode(uinfo[1], encoding),
+ EncodingTools.urlDecode(hinfo[0], encoding),
+ port);
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC INNER CLASSES */
+ /* ====================================================================== */
+
+ /**
+ * <p>The {@link Location.Schemes Schemes} class represents an unmodifiable
+ * ordered collection of {@link String} schemes for a {@link Location}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+ public static class Schemes extends AbstractList implements Encodable {
+ /** <p>All the {@link String} schemes in order.</p> */
+ private final String schemes[];
+ /** <p>The {@link String} representation of this instance.</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Schemes} instance.</p>
+ */
+ private Schemes(List schemes) {
+ final int size = schemes.size();
+ this.schemes = (String []) schemes.toArray(new String[size]);
+ this.string = EncodingTools.toString(this);
+ }
+
+ /**
+ * <p>Return the {@link String} scheme at the specified index.</p>
+ */
+ public Object get(int index) {
+ return this.schemes[index];
+ }
+
+ /**
+ * <p>Return the number of {@link String} schemes contained by this
+ * {@link Location.Schemes Schemes} instance.</p>
+ */
+ public int size() {
+ return this.schemes.length;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Location.Schemes Schemes} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Location.Schemes Schemes} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ final StringBuffer buffer = new StringBuffer();
+ for (int x = 0; x < this.schemes.length; x ++) {
+ buffer.append(':');
+ buffer.append(EncodingTools.urlEncode(this.schemes[x], encoding));
+ }
+ return buffer.substring(1);
+ }
+
+ /**
+ * <p>Return the hash code value for this
+ * {@link Location.Schemes Schemes} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Location.Schemes Schemes} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, it is a {@link Location.Schemes Schemes}
+ * instance, and its {@link #toString() string representation} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Schemes)) {
+ return this.string.equals(((Schemes) object).string);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /* ====================================================================== */
+
+ /**
+ * <p>The {@link Location.Authority Authority} class represents the autority
+ * and user information for a {@link Location}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+ public static class Authority implements Encodable {
+ /** <p>The username of this instance (decoded).</p> */
+ private final String username;
+ /** <p>The password of this instance (decoded).</p> */
+ private final String password;
+ /** <p>The host name of this instance (decoded).</p> */
+ private final String host;
+ /** <p>The port number of this instance.</p> */
+ private final int port;
+ /** <p>The encoded host and port representation.</p> */
+ private final String hostinfo;
+ /** <p>The encoded string representation of this instance.</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Location.Authority Authority} instance.</p>
+ */
+ private Authority(String user, String pass, String host, int port) {
+ this.username = user;
+ this.password = pass;
+ this.host = host;
+ this.port = port;
+ try {
+ this.hostinfo = this.getHostInfo(DEFAULT_ENCODING);
+ this.string = this.toString(DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Default encoding \"" + DEFAULT_ENCODING
+ + "\" not supported by the platform";
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Returns the decoded user name.</p>
+ */
+ public String getUsername() {
+ return this.username;
+ }
+
+ /**
+ * <p>Returns the decoded password.</p>
+ */
+ public String getPassword() {
+ return this.password;
+ }
+
+ /**
+ * <p>Returns the &quot;user info&quot; field.</p>
+ *
+ * <p>This method will concatenate the username and password using the
+ * colon character and return a <b>non-null</b> {@link String} only if
+ * both of them are <b>non-null</b>.</p>
+ */
+ public String getUserInfo() {
+ if ((this.username == null) || (this.password == null)) return null;
+ return this.username + ':' + this.password;
+ }
+
+ /**
+ * <p>Returns the decoded host name.</p>
+ */
+ public String getHost() {
+ return this.host;
+ }
+
+ /**
+ * <p>Returns the port number.</p>
+ */
+ public int getPort() {
+ return this.port;
+ }
+
+ /**
+ * <p>Returns the host info part of the
+ * {@link Location.Authority Authority}.</p>
+ *
+ * <p>This is the encoded representation of the
+ * {@link #getUsername() user name} optionally follwed by the colon (:)
+ * character and the encoded {@link #getPassword() password}.</p>
+ */
+ public String getHostInfo() {
+ return this.hostinfo;
+ }
+
+ /**
+ * <p>Returns the host info part of the
+ * {@link Location.Authority Authority} using the specified character
+ * encoding.</p>
+ *
+ * <p>This is the encoded representation of the
+ * {@link #getUsername() user name} optionally follwed by the colon (:)
+ * character and the encoded {@link #getPassword() password}.</p>
+ */
+ public String getHostInfo(String encoding)
+ throws UnsupportedEncodingException {
+ final StringBuffer hostinfo = new StringBuffer();
+ hostinfo.append(EncodingTools.urlEncode(this.host, encoding));
+ if (port >= 0) hostinfo.append(':').append(port);
+ return hostinfo.toString();
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Location.Authority Authority} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Location.Authority Authority} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ final StringBuffer buffer;
+ if (this.username != null) {
+ buffer = new StringBuffer();
+ buffer.append(EncodingTools.urlEncode(this.username, encoding));
+ if (this.password != null) {
+ buffer.append(':');
+ buffer.append(EncodingTools.urlEncode(this.password, encoding));
+ }
+ } else {
+ buffer = null;
+ }
+
+ if (buffer == null) return this.getHostInfo(encoding);
+ buffer.append('@').append(this.getHostInfo(encoding));
+ return buffer.toString();
+ }
+
+ /**
+ * <p>Return the hash code value for this
+ * {@link Location.Authority Authority} instance.</p>
+ */
+ public int hashCode() {
+ return this.hostinfo.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Location.Authority Authority} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, it is a {@link Location.Authority Authority}
+ * instance, and its {@link #getHostInfo() host info} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Authority)) {
+ return this.hostinfo.equals(((Authority) object).hostinfo);
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java
new file mode 100644
index 000000000..3ffa0bac7
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java
@@ -0,0 +1,474 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.location;
+
+import it.could.util.StringTools;
+import it.could.util.encoding.Encodable;
+import it.could.util.encoding.EncodingTools;
+
+import java.io.UnsupportedEncodingException;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * <p>The {@link Parameters Parameters} class represents a never empty and
+ * immutable {@link List} of {@link Parameters.Parameter Parameter} instances,
+ * normally created parsing a query string.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class Parameters extends AbstractList implements Encodable {
+
+ /** <p>The default delimiter for a {@link Parameters} instance.</p> */
+ public static final char DEFAULT_DELIMITER = '&';
+
+ /** <p>All the {@link Parameter}s in order.</p> */
+ private final Parameter parameters[];
+ /** <p>The {@link Map} view over all parameters (names are keys).</p> */
+ private final Map map;
+ /** <p>The {@link Set} of all parameter names.</p> */
+ final Set names;
+ /** <p>The character delimiting different parameters.</p> */
+ private final char delimiter;
+ /** <p>The encoded {@link String} representation of this.</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Parameters Parameters} instance from
+ * a {@link List} of {@link Parameters.Parameter Parameter} instances
+ * using the {@link #DEFAULT_DELIMITER default parameter delimiter}.</p>
+ *
+ * @throws NullPointerExceptoin if the {@link List} was <b>null</b>.
+ * @throws IllegalArgumentException if the {@link List} was empty.
+ * @throws ClassCastException if any of the elements in the {@link List} was
+ * not a {@link Parameters.Parameter Parameter}.
+ */
+ public Parameters(List parameters) {
+ this(parameters, DEFAULT_DELIMITER);
+ }
+
+ /**
+ * <p>Create a new {@link Parameters Parameters} instance from
+ * a {@link List} of {@link Parameters.Parameter Parameter} instances
+ * using the specified character as the parameters delimiter.</p>
+ *
+ * @throws NullPointerExceptoin if the {@link List} was <b>null</b>.
+ * @throws IllegalArgumentException if the {@link List} was empty.
+ * @throws ClassCastException if any of the elements in the {@link List} was
+ * not a {@link Parameters.Parameter Parameter}.
+ */
+ public Parameters(List parameters, char delimiter) {
+ if (parameters.size() == 0) throw new IllegalArgumentException();
+ final Parameter array[] = new Parameter[parameters.size()];
+ final Map map = new HashMap();
+ for (int x = 0; x < array.length; x ++) {
+ final Parameter parameter = (Parameter) parameters.get(x);
+ final String key = parameter.getName();
+ List values = (List) map.get(key);
+ if (values == null) {
+ values = new ArrayList();
+ map.put(key, values);
+ }
+ values.add(parameter.getValue());
+ array[x] = parameter;
+ }
+
+ /* Make all parameter value lists unmodifiable */
+ for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
+ final Map.Entry entry = (Map.Entry) iter.next();
+ final List list = (List) entry.getValue();
+ entry.setValue(Collections.unmodifiableList(list));
+ }
+
+ /* Store the current values */
+ this.delimiter = delimiter;
+ this.map = Collections.unmodifiableMap(map);
+ this.names = Collections.unmodifiableSet(map.keySet());
+ this.parameters = array;
+ this.string = EncodingTools.toString(this);
+ }
+
+ /* ====================================================================== */
+ /* STATIC CONSTRUCTION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Utility method to create a new {@link Parameters} instance from a
+ * {@link List} of {@link Parameters.Parameter Parameter} instances.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified {@link List} was <b>null</b>, empty
+ * or did not contain any {@link Parameters.Parameter Parameter}.
+ * @throws ClassCastException if any of the elements in the {@link List} was
+ * not a {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters create(List parameters) {
+ return create(parameters, DEFAULT_DELIMITER);
+ }
+
+ /**
+ * <p>Utility method to create a new {@link Parameters} instance from a
+ * {@link List} of {@link Parameters.Parameter Parameter} instances.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified {@link List} was <b>null</b>, empty
+ * or did not contain any {@link Parameters.Parameter Parameter}.
+ * @throws ClassCastException if any of the elements in the {@link List} was
+ * not a {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters create(List parameters, char delimiter) {
+ if (parameters == null) return null;
+ final List dedupes = new ArrayList();
+ for (Iterator iter = parameters.iterator(); iter.hasNext(); ) {
+ Object next = iter.next();
+ if (dedupes.contains(next)) continue;
+ dedupes.add(next);
+ }
+ if (dedupes.size() == 0) return null;
+ return new Parameters(dedupes, delimiter);
+ }
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters} instance using the {@link #DEFAULT_DELIMITER default
+ * parameter delimiter}.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified string was <b>null</b>, empty or
+ * did not contain any {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters parse(String parameters) {
+ try {
+ return parse(parameters, DEFAULT_DELIMITER, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters} instance using the specified character as the
+ * parameters delimiter.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified string was <b>null</b>, empty or
+ * did not contain any {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters parse(String parameters, char delimiter) {
+ try {
+ return parse(parameters, delimiter, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters} instance using the {@link #DEFAULT_DELIMITER default
+ * parameter delimiter}.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified string was <b>null</b>, empty or
+ * did not contain any {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters parse(String parameters, String encoding)
+ throws UnsupportedEncodingException {
+ return parse(parameters, DEFAULT_DELIMITER, encoding);
+ }
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters} instance using the specified character as the
+ * parameters delimiter.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters} instance or
+ * <b>null</b> if the specified string was <b>null</b>, empty or
+ * did not contain any {@link Parameters.Parameter Parameter}.
+ */
+ public static Parameters parse(String parameters, char delimiter,
+ String encoding)
+ throws UnsupportedEncodingException {
+ if (parameters == null) return null;
+ if (parameters.length() == 0) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ final String split[] = StringTools.splitAll(parameters, delimiter);
+ final List list = new ArrayList();
+ for (int x = 0; x < split.length; x ++) {
+ if (split[x] == null) continue;
+ if (split[x].length() == 0) continue;
+ Parameter parameter = Parameter.parse(split[x], encoding);
+ if (parameter != null) list.add(parameter);
+ }
+ if (list.size() == 0) return null;
+ return new Parameters(list, delimiter);
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC EXPOSED METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the number of {@link Parameters.Parameter Parameter}s
+ * contained by this instance.</p>
+ */
+ public int size() {
+ return this.parameters.length;
+ }
+
+ /**
+ * <p>Return the {@link Parameters.Parameter Parameter} stored by this\
+ * instance at the specified index.</p>
+ */
+ public Object get(int index) {
+ return this.parameters[index];
+ }
+
+ /**
+ * <p>Return an immutable {@link Set} of {@link String}s containing all
+ * known {@link Parameters.Parameter Parameter}
+ * {@link Parameters.Parameter#getName() names}.</p>
+ */
+ public Set getNames() {
+ return this.names;
+ }
+
+ /**
+ * <p>Return the first {@link String} value associated with the
+ * specified parameter name, or <b>null</b>.</p>
+ */
+ public String getValue(String name) {
+ final List values = (List) this.map.get(name);
+ return values == null ? null : (String) values.get(0);
+ }
+
+ /**
+ * <p>Return an immutable {@link List} of all {@link String} values
+ * associated with the specified parameter name, or <b>null</b>.</p>
+ */
+ public List getValues(String name) {
+ return (List) this.map.get(name);
+ }
+
+ /* ====================================================================== */
+ /* OBJECT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Parameters Parameters} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Parameters Parameters} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ StringBuffer buffer = new StringBuffer();
+ for (int x = 0; x < this.parameters.length; x ++) {
+ buffer.append(this.delimiter);
+ buffer.append(this.parameters[x].toString(encoding));
+ }
+ return buffer.substring(1);
+ }
+
+ /**
+ * <p>Return the hash code value of this
+ * {@link Parameters Parameters} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Parameters Parameters} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, it is a {@link Parameters Parameters}
+ * instance, and its {@link #toString() string representation} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Parameters)) {
+ return this.string.equals(((Parameters) object).string);
+ } else {
+ return false;
+ }
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC INNER CLASSES */
+ /* ====================================================================== */
+
+ /**
+ * <p>The {@link Parameters.Parameter Parameter} class represents a single
+ * parameter either parsed from a query string or a path element.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+ public static class Parameter implements Encodable {
+ /** <p>The name of the parameter (decoded).</p> */
+ private final String name;
+ /** <p>The value of the parameter (decoded).</p> */
+ private final String value;
+ /** <p>The encoded {@link String} representation of this.</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Parameters.Parameter Parameter} given an
+ * encoded parameter name and value.</p>
+ *
+ * @throws NullPointerException if the name was <b>null</b>.
+ * @throws IllegalArgumentException if the name was an empty string.
+ */
+ public Parameter(String name, String value) {
+ if (name == null) throw new NullPointerException();
+ if (name.length() == 0) throw new IllegalArgumentException();
+ this.name = name;
+ this.value = value;
+ this.string = EncodingTools.toString(this);
+ }
+
+ /* ================================================================== */
+ /* STATIC CONSTRUCTION METHODS */
+ /* ================================================================== */
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters.Parameter} instance.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters.Parameter}
+ * instance or <b>null</b> if the specified string was
+ * <b>null</b> or empty.
+ */
+ public static Parameter parse(String parameter)
+ throws UnsupportedEncodingException {
+ try {
+ return parse(parameter, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified parameters {@link String} into a
+ * {@link Parameters.Parameter} instance.</p>
+ *
+ * @return a <b>non-null</b> and not empty {@link Parameters.Parameter}
+ * instance or <b>null</b> if the specified string was
+ * <b>null</b> or empty.
+ */
+ public static Parameter parse(String parameter, String encoding)
+ throws UnsupportedEncodingException {
+ if (parameter == null) return null;
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ String split[] = StringTools.splitOnce(parameter, '=', false);
+ if (split[0] == null) return null;
+ return new Parameter(split[0], split[1]);
+ }
+
+ /* ================================================================== */
+ /* PUBLIC EXPOSED METHODS */
+ /* ================================================================== */
+
+ /**
+ * <p>Return the URL-decoded name of this
+ * {@link Parameters.Parameter Parameter} instance.</p>
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * <p>Return the URL-decoded value of this
+ * {@link Parameters.Parameter Parameter} instance.</p>
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /* ================================================================== */
+ /* OBJECT METHODS */
+ /* ================================================================== */
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Parameters.Parameter Parameter} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Parameters.Parameter Parameter} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ if (this.value != null) {
+ return EncodingTools.urlEncode(this.name, encoding) + "=" +
+ EncodingTools.urlEncode(this.value, encoding);
+ } else {
+ return EncodingTools.urlEncode(this.name, encoding);
+ }
+ }
+
+ /**
+ * <p>Return the hash code value for this
+ * {@link Parameters.Parameter Parameter} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Parameters.Parameter Parameter} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, it is a {@link Parameters.Parameter Parameter}
+ * instance, and its {@link #toString() string representation} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Parameter)) {
+ return this.string.equals(((Parameter) object).string);
+ } else {
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java
new file mode 100644
index 000000000..722a0d46b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java
@@ -0,0 +1,559 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.util.location;
+
+import it.could.util.StringTools;
+import it.could.util.encoding.Encodable;
+import it.could.util.encoding.EncodingTools;
+
+import java.io.UnsupportedEncodingException;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+
+/**
+ * <p>The {@link Path Path} class is an ordered collection of
+ * {@link Path.Element Element} instances representing a path
+ * structure.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class Path extends AbstractList implements Encodable {
+
+ /** <p>The array of {@link Path.Element Element}s.</p> */
+ private final Element paths[];
+ /** <p>The current {@link Parameters} instance or <b>null</b>.</p> */
+ private final Parameters parameters;
+ /** <p>A flag indicating whether this path is absolute or not.</p> */
+ private final boolean absolute;
+ /** <p>A flag indicating if this path is a collection or not.</p> */
+ private final boolean collection;
+ /** <p>The {@link String} representation of this (encoded).</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Path Path} instance.</p>
+ *
+ * @throws ClassCastException if any of the elements in the {@link List}
+ * was not a {@link Path.Element Element}.
+ */
+ public Path(List elements, boolean absolute, boolean collection) {
+ this(elements, absolute, collection, null);
+ }
+
+ /**
+ * <p>Create a new {@link Path Path} instance.</p>
+ *
+ * @throws ClassCastException if any of the elements in the {@link List}
+ * was not a {@link Path.Element Element}.
+ */
+ public Path(List elements, boolean absolute, boolean collection,
+ Parameters parameters) {
+ final Stack resolved = resolve(null, absolute, elements);
+ final Element array[] = new Element[resolved.size()];
+ this.paths = (Element []) resolved.toArray(array);
+ this.parameters = parameters;
+ this.absolute = absolute;
+ this.collection = collection;
+ this.string = EncodingTools.toString(this);
+ }
+
+ /* ====================================================================== */
+ /* STATIC CONSTRUCTION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} structure.</p>
+ */
+ public static Path parse(String path) {
+ try {
+ return parse(path, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} structure.</p>
+ */
+ public static Path parse(String path, String encoding)
+ throws UnsupportedEncodingException {
+ final List params = new ArrayList();
+ final List elems = new ArrayList();
+
+ /* No path, flog it! */
+ if ((path == null) || (path.length() == 0)) {
+ return new Path(elems, false, false, null);
+ }
+
+ /* Check for a proper encoding */
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+
+ /* Split up the path structure into its path element components */
+ final String split[] = StringTools.splitAll(path, '/');
+
+ /* Check if this path is an absolute path */
+ final boolean absolute = path.charAt(0) == '/';
+
+ /* Check every single path element and append it to the current one */
+ Element element = null;
+ for (int x = 0; x < split.length; x++) {
+ if (split[x] == null) continue; /* Collapse double slashes */
+ element = parsePath(split[x], params, encoding);
+ if (element != null) elems.add(element);
+ }
+
+ /* Check if this is a collection */
+ final boolean collection = ((split[split.length - 1] == null)
+ || (element == null)
+ || element.getName().equals(".")
+ || element.getName().equals(".."));
+
+ /* Setup the last path in our chain and return the first one */
+ final Parameters parameters = Parameters.create(params, ';');
+ return new Path(elems, absolute, collection, parameters);
+ }
+
+ /* ====================================================================== */
+
+ /**
+ * <p>Parse a single path element like <code>path!extra;param</code>.</p>
+ */
+ private static Element parsePath(String path, List parameters,
+ String encoding)
+ throws UnsupportedEncodingException {
+ final int pathEnds = StringTools.findFirst(path, "!;");
+ final Element element;
+
+ if (pathEnds < 0) {
+ element = new Element(EncodingTools.urlDecode(path, encoding), null);
+ } else if (path.charAt(pathEnds) == ';') {
+ // --> pathname;pathparameter
+ final String name = path.substring(0, pathEnds);
+ final String param = path.substring(pathEnds + 1);
+ final Parameters params = Parameters.parse(param, ';', encoding);
+ if (params != null) parameters.addAll(params);
+ element = new Element(EncodingTools.urlDecode(name, encoding), null);
+ } else {
+ // --> pathname!extra...
+ final String name = path.substring(0, pathEnds);
+ final String more = path.substring(pathEnds + 1);
+ final String split[] = StringTools.splitOnce(more, ';', false);
+ final Parameters params = Parameters.parse(split[1], ';', encoding);
+ if (params != null) parameters.addAll(params);
+ element = new Element(EncodingTools.urlDecode(name, encoding),
+ EncodingTools.urlDecode(split[0], encoding));
+ }
+ if (element.toString().length() == 0) return null;
+ return element;
+ }
+
+ /* ====================================================================== */
+ /* RESOLUTION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Resolve the specified {@link Path} against this one.</p>
+ */
+ public Path resolve(Path path) {
+ /* Merge the parameters */
+ final List params = new ArrayList();
+ if (this.parameters != null) params.addAll(this.parameters);
+ if (path.parameters != null) params.addAll(path.parameters);
+ final Parameters parameters = Parameters.create(params, ';');
+
+ /* No path, return this instance */
+ if (path == null) return this;
+
+ /* If the target is absolute, only merge the parameters */
+ if (path.absolute)
+ return new Path(path, true, path.collection, parameters);
+
+ /* Resolve the path */
+ final Stack source = new Stack();
+ source.addAll(this);
+ if (! this.collection && (source.size() > 0)) source.pop();
+ final List resolved = resolve(source, this.absolute, path);
+
+ /* Figure out if the resolved path is a collection and return it */
+ final boolean c = path.size() == 0 ? this.collection : path.collection;
+ return new Path(resolved, this.absolute, c, parameters);
+ }
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} and resolve it
+ * against this one.</p>
+ */
+ public Path resolve(String path) {
+ try {
+ return this.resolve(parse(path, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} and resolve it
+ * against this one.</p>
+ *
+ * @throws NullPointerException if the path {@link String} was <b>null</b>.
+ */
+ public Path resolve(String path, String encoding)
+ throws UnsupportedEncodingException {
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ if (path == null) return this;
+ return this.resolve(parse(path, encoding));
+ }
+
+ /* ====================================================================== */
+
+ private static Stack resolve(Stack stack, boolean absolute, List elements) {
+ /* If we have no source stack we create a new empty one */
+ if (stack == null) stack = new Stack();
+ /* A flag indicating whether we are at the "root" path element. */
+ boolean atroot = absolute && stack.empty();
+ /* Iterate through the current path elements to see what to do. */
+ for (Iterator iter = elements.iterator(); iter.hasNext(); ) {
+ final Element element = (Element) iter.next();
+ /* If this is the "." (current) path element, skip it. */
+ if (".".equals(element.getName())) continue;
+ /* If this is the ".." (parent) path element, it gets nasty. */
+ if ("..".equals(element.getName())) {
+ /* The root path's parent is always itself */
+ if (atroot) continue;
+ /* We're not at root and have the stack, relative ".." */
+ if (stack.size() == 0) {
+ stack.push(element);
+ /* We're not at root, but we have stuff in the stack */
+ } else {
+ /* Get the last element in the stack */
+ final Element prev = (Element) stack.peek();
+ /* If the last element is "..", add another one */
+ if ("..".equals(prev.getName())) stack.push(element);
+ /* The last element was not "..", pop it out */
+ else stack.pop();
+ /* If absoulte and stack is empty, we're at root */
+ if (absolute) atroot = stack.size() == 0;
+ }
+ } else {
+ /* Normal element processing follows... */
+ stack.push(element);
+ atroot = false;
+ }
+ }
+ return stack;
+ }
+
+ /* ====================================================================== */
+ /* RELATIVIZATION METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} and relativize
+ * it against this one.</p>
+ */
+ public Path relativize(String path) {
+ try {
+ return this.relativize(parse(path, DEFAULT_ENCODING));
+ } catch (UnsupportedEncodingException exception) {
+ final String message = "Unsupported encoding " + DEFAULT_ENCODING;
+ final InternalError error = new InternalError(message);
+ throw (InternalError) error.initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Parse the specified {@link String} into a {@link Path} and relativize
+ * it against this one.</p>
+ */
+ public Path relativize(String path, String encoding)
+ throws UnsupportedEncodingException {
+ if (encoding == null) encoding = DEFAULT_ENCODING;
+ return this.relativize(parse(path, encoding));
+ }
+
+ /**
+ * <p>Retrieve the relativization path from this {@link Path} to the
+ * specified {@link Path}.</p>
+ */
+ public Path relativize(Path path) {
+ /* No matter what, always return the aggregate of all parameters */
+ final List parameters = new ArrayList();
+ if (this.parameters != null) parameters.addAll(this.parameters);
+ if (path.parameters != null) parameters.addAll(path.parameters);
+ final Parameters params = Parameters.create(parameters, ';');
+
+ /* We are absolute and the specified path is absolute, we process */
+ if ((path.absolute) && (this.absolute)) {
+ /* Find the max number of paths we should examine */
+ final int num = this.collection ? this.size() : this.size() - 1;
+
+ /* Process the two absolute paths to check common elements */
+ int skip = 0;
+ for (int x = 0; (x < num) && (x < path.size()); x ++) {
+ if (path.paths[x].equals(this.paths[x])) skip ++;
+ else break;
+ }
+
+ /* Figure out if the resulting path is a collection */
+ final boolean collection;
+ if (path.size() > skip) collection = path.collection;
+ else if (this.size() > skip) collection = true;
+ else collection = this.collection;
+
+ /* Recreate the path to return by adding ".." and the paths */
+ final List elems = new ArrayList();
+ for (int x = skip; x < num; x ++) elems.add(new Element("..", null));
+ elems.addAll(path.subList(skip, path.size()));
+ return new Path(elems, false, collection);
+ }
+
+ /*
+ * Here we are in one of the following cases:
+ * - the specified path is already relative, so why bother?
+ * - we are relative and the specified path is absolute: in this case
+ * we can't possibly know how far away we are located from the root
+ * so, we only have one option, to return the absolute path.
+ * In all cases, though, before returning the specified path, we just
+ * merge ours and the path's parameters.
+ */
+ if (this.absolute && (! path.absolute)) {
+ /*
+ * Ok, let's bother, we're absolute and the specified is not. This
+ * means that if we resolve the path, we can find another absolute
+ * path, and therefore we can do a better job at relativizin it.
+ */
+ return this.relativize(this.resolve(path));
+ }
+ /* We'll never going to be able to do better than this */
+ return new Path(path, path.absolute, path.collection, params);
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC EXPOSED METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link Path.Element Element} instance at
+ * the specified index.</p>
+ */
+ public Object get(int index) {
+ return this.paths[index];
+ }
+
+ /**
+ * <p>Return the number of {@link Path.Element Element}
+ * instances contained by this instance.</p>
+ */
+ public int size() {
+ return this.paths.length;
+ }
+
+ /**
+ * <p>Checks if this {@link Path Path} instance represents
+ * an absolute path.</p>
+ */
+ public boolean isAbsolute() {
+ return this.absolute;
+ }
+
+ /**
+ * <p>Checks if this {@link Path Path} instance represents
+ * a collection.</p>
+ */
+ public boolean isCollection() {
+ return this.collection;
+ }
+
+ /**
+ * <p>Returns the collection of {@link Parameters Parameters}
+ * contained by this instance or <b>null</b>.</p>
+ */
+ public Parameters getParameters() {
+ return this.parameters;
+ }
+
+ /* ====================================================================== */
+ /* OBJECT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Path Path} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Path Path} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ StringBuffer buffer = new StringBuffer();
+ if (this.absolute) buffer.append('/');
+ final int last = this.paths.length - 1;
+ for (int x = 0; x < last; x ++) {
+ buffer.append(this.paths[x].toString(encoding)).append('/');
+ }
+ if (last >= 0) {
+ buffer.append(this.paths[last].toString(encoding));
+ if (this.collection) buffer.append('/');
+ }
+ if (this.parameters != null)
+ buffer.append(';').append(this.parameters.toString(encoding));
+ return buffer.toString();
+ }
+
+ /**
+ * <p>Return the hash code value of this
+ * {@link Path Path} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Path Path} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, is a {@link Path Path}
+ * instance and its {@link #toString() string representation} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Path)) {
+ return this.string.equals(((Path) object).string);
+ }
+ return false;
+ }
+
+ /* ====================================================================== */
+ /* PUBLIC INNER CLASSES */
+ /* ====================================================================== */
+
+ /**
+ * <p>The {@link Path.Element Element} class represents a path
+ * element within the {@link Path Path} structure.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+ public static class Element implements Encodable {
+
+ /** <p>The name of this path element (decoded).</p> */
+ private final String name;
+ /** <p>The extra path information of this path element (decoded).</p> */
+ private final String extra;
+ /** <p>The {@link String} representation of this (encoded).</p> */
+ private final String string;
+
+ /**
+ * <p>Create a new {@link Path.Element Element} instance given its
+ * url-decoded components name and extra.</p>
+ *
+ * @throws NullPointerException if the specified name was <b>null</b>.
+ */
+ public Element(String name, String extra) {
+ if (name == null) throw new NullPointerException("Null path name");
+ this.name = name;
+ this.extra = extra;
+ this.string = EncodingTools.toString(this);
+ }
+
+ /* ================================================================== */
+ /* PUBLIC EXPOSED METHODS */
+ /* ================================================================== */
+
+ /**
+ * <p>Return the url-decoded {@link String} name of this
+ * {@link Path.Element Element}.</p>
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * <p>Return the url-decoded {@link String} extra path of this
+ * {@link Path.Element Element}.</p>
+ */
+ public String getExtra() {
+ return this.extra;
+ }
+
+ /* ================================================================== */
+ /* OBJECT METHODS */
+ /* ================================================================== */
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Path.Element Element} instance.</p>
+ */
+ public String toString() {
+ return this.string;
+ }
+
+ /**
+ * <p>Return the URL-encoded {@link String} representation of this
+ * {@link Path.Element Element} instance using the specified
+ * character encoding.</p>
+ */
+ public String toString(String encoding)
+ throws UnsupportedEncodingException {
+ final StringBuffer buffer = new StringBuffer();
+ buffer.append(EncodingTools.urlEncode(this.name, encoding));
+ if (this.extra != null) {
+ buffer.append('!');
+ buffer.append(EncodingTools.urlEncode(this.extra, encoding));
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * <p>Return the hash code value of this
+ * {@link Path.Element Element} instance.</p>
+ */
+ public int hashCode() {
+ return this.string.hashCode();
+ }
+
+ /**
+ * <p>Check if the specified {@link Object} is equal to this
+ * {@link Path.Element Element} instance.</p>
+ *
+ * <p>The specified {@link Object} is considered equal to this one if
+ * it is <b>non-null</b>, is a {@link Path.Element Element}
+ * instance and its {@link #toString() string representation} equals
+ * this one's.</p>
+ */
+ public boolean equals(Object object) {
+ if ((object != null) && (object instanceof Element)) {
+ return this.string.equals(((Element) object).string);
+ }
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html
new file mode 100644
index 000000000..155907e08
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <title>Location Utilities</title>
+ </head>
+ <body>
+ <p>
+ This package contains a number of utility classes to parse and
+ work with URLs.
+ </p>
+ <p>
+ The {@link java.net.URL} class already provides most of the functionality
+ covered by this package, but certain limitations in its implementation
+ (for example, all schemes <i>must</i> be registered with the
+ {java.net.URLStreamHandler} class before they can be used), prompted
+ the re-development of a similar API.
+ </p>
+ <p>
+ For further details on what the different classes in this package mean
+ and how they interact, see the {@link it.could.util.location.Location}
+ class documentation, but as a reference, this is a picture outlining
+ the structure:
+ </p>
+ <div align="center">
+ <a href="url.pdf" target="_new" title="PDF Version">
+ <img src="url.gif" alt="URL components" border="0">
+ </a>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif
new file mode 100644
index 000000000..1c87bd9c6
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif
Binary files differ
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf
new file mode 100644
index 000000000..f970fe310
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf
@@ -0,0 +1,884 @@
+%PDF-1.4 %âãÏÓ
+1 0 obj << /Type /Catalog /Pages 2 0 R /Metadata 408 0 R >> endobj 2 0 obj << /Type /Pages /Kids [ 5 0 R ] /Count 1 >> endobj 3 0 obj << /ModDate (D:20060402130123+01'00') /CreationDate (D:20060402130122+01'00') /Producer (Adobe PDF library 5.00) /Creator (Adobe Illustrator 10.0.3) >> endobj 5 0 obj << /Type /Page /MediaBox [ 0 0 416.69336 111.96875 ] /Parent 2 0 R /PieceInfo << /Illustrator 7 0 R >> /LastModified (D:20060402130122+01'00') /ArtBox [ 0 0 416.69434 111.96875 ] /Group 397 0 R /Thumb 398 0 R /Contents 400 0 R /Resources << /ColorSpace << /CS0 32 0 R /CS1 33 0 R /CS2 48 0 R >> /Font << /TT0 34 0 R /TT1 37 0 R /TT2 258 0 R >> /Pattern << /P0 41 0 R /P1 50 0 R /P2 58 0 R /P3 66 0 R /P4 74 0 R /P5 82 0 R /P6 90 0 R /P7 98 0 R /P8 106 0 R /P9 114 0 R /P10 122 0 R /P11 130 0 R /P12 138 0 R /P13 146 0 R /P14 154 0 R /P15 162 0 R /P16 170 0 R /P17 178 0 R /P18 186 0 R /P19 194 0 R /P20 202 0 R /P21 210 0 R /P22 218 0 R /P23 226 0 R /P24 234 0 R /P25 243 0 R /P26 251 0 R /P27 262 0 R /P28 270 0 R /P29 278 0 R /P30 286 0 R /P31 294 0 R /P32 302 0 R /P33 310 0 R /P34 318 0 R /P35 326 0 R /P36 334 0 R /P37 342 0 R /P38 350 0 R /P39 358 0 R /P40 366 0 R /P41 374 0 R /P42 382 0 R /P43 390 0 R >> /ExtGState << /GS0 40 0 R /GS1 241 0 R >> /ProcSet [ /PDF /Text ] >> >> endobj 7 0 obj << /Private 8 0 R /LastModified (D:20060402130122+01'00') >> endobj 8 0 obj << /CreatorVersion 10 /ContainerVersion 9 /RoundtripVersion 10 /Options 9 0 R /AIMetaData 10 0 R /AIPDFPrivateData1 11 0 R /AIPDFPrivateData2 12 0 R /AIPDFPrivateData3 14 0 R /AIPDFPrivateData4 16 0 R /AIPDFPrivateData5 18 0 R /AIPDFPrivateData6 20 0 R /AIPDFPrivateData7 22 0 R /AIPDFPrivateData8 24 0 R /AIPDFPrivateData9 26 0 R /AIPDFPrivateData10 28 0 R /AIPDFPrivateData11 30 0 R /NumBlock 11 >> endobj 9 0 obj << /OptionSet 4 /Compatibility 5 /EmbedFonts true /SubsetFontsBelow true /SubsetFontsRatio 100 /Thumbnail true /EmbedICCProfile true /cCompression true /cCompKind 3 /cCompQuality 2 /cResolution false /cRes 300 /gCompression true /gCompKind 3 /gCompQuality 2 /gResolution false /gRes 300 /mCompression true /mCompKind 3 /mResolution false /mRes 1200 /CompressArt true >> endobj 10 0 obj << /Length 1174 >> stream
+%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 10.0 %%AI8_CreatorVersion: 10.0 %%For: (Pier Fumagalli) (VNU Business Publications) %%Title: (url.ai) %%CreationDate: 2/4/06 13:01 %%BoundingBox: 0 0 417 112 %%HiResBoundingBox: 0 0 416.6943 111.9688 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 6.0 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%CMYKProcessColor: 1 1 1 1 ([Registration]) %%AI6_ColorSeparationSet: 1 1 (AI6 Default Color Separation Set) %%+ Options: 1 16 0 1 0 1 0 0 0 0 1 1 1 18 0 0 0 0 0 0 0 0 -1 -1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 2 3 4 %%+ PPD: 1 21 0 0 60 45 2 2 1 0 0 1 0 0 0 0 0 0 0 0 -1 -1 () %AI3_TemplateBox: 208.5 55.4688 208.5 55.4688 %AI3_TileBox: 30 -698.9199 582 31.0801 %AI3_DocumentPreview: None %AI5_ArtSize: 416.6929 111.9685 %AI5_RulerUnits: 1 %AI9_ColorModel: 2 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -135 278.9688 2 1422 851 26 1 1 11 42 1 0 1 1 1 0 %AI5_OpenViewLayers: 7 %%PageOrigin:30 -698.9199 %%AI3_PaperRect:-30 761 582 -31 %%AI3_Margin:30 -31 -30 31 %AI7_GridSettings: 28.3465 20 28.3465 20 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 0 %%EndComments endstream endobj 11 0 obj << /Length 6360 >> stream
+%%BoundingBox: 0 0 417 112 %%HiResBoundingBox: 0 0 416.6943 111.9688 %AI7_Thumbnail: 128 36 8 %%BeginData: 6026 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD1CFF7DFD48FFA8FD35FF7D52FD23FF52FD23FF527DFD29FFA8A2 %7DA27DA27DA27DA27DA27DA27DA27DA27DA27DA27DA27DA1FFA87DA77DA7 %7DA77DA77DA77DA77DA77DA77DA77DA77DA77DA77DA77DA77DA77DA77DA7 %7DA77DA77DA77DA77DA8FF7EFD187DA8FD14FFA8FD08FFA89BA29BC49BA1 %52C49BA29BC49BA29BC49BA29B7D77A29BC49BFF83AD82A782AD828358AD %82A782AD82A782AD82A782AD828358AD82A782AD82A782AD82A782AD8283 %82AD82A783FF7777537D7777527D7777537D7777537D777752777777537D %77A8FD13FF7D7DFD08FFCBCAA2CBA2CA5277A2CAA2CBA2CAA2CBA2CAA2CB %52A2A2CBA2CAFFFFADAEA7AEADAE277CADAEA7AEADAEA7AEADAEA7AEADAE %5252ADAEA7AEADAEA7AEADAEA7AEAD5252AEADAEA7CFFFA87DA27EA22752 %7EA27DA27EA27DA27EA27D7727A27DA27EA2A8FD0EFF7D59595A5959595A %5959597DFFFFFF7D77777677777776777777767776CA5277777776777777 %767752FF7C7C587C587C587C587C587C587C58837C7C587C587C587C587C %587C587C58837C7C587C587C587C587C587C52FF52524C534C524C534C52 %4C52A2524C534C524C534C524C534C7DFD0FFF858459AF858B8484598BA8 %FFFFFFA2C4A2779BC4A2C49BC477A19BC4CBA29BC477C49BC4A2C477A2FF %AE82AD58AD82AD83AD82AD58AD82ADA8AE82AD58AD82AD83AD82AD58AD82 %ADA8AE82AD7CAD82AD83AD825882ADFFA27777527D7777777D527777FF7D %FD06777D7777527DA8FD06FFA8A8FD06FFA87E272E59A9847D272E59A8FF %FFFFA877A1277D77A2A27D77274C7D7DFF7D7D7727777DA1A2772777FFA7 %7D7C277C7D83AE7C7D7C277C7D7CCFA87D7C277C7D83AE7C7D7C277C7D7C %CFA87C5827587C7CAE7C7C27837DFF777D5227527D7D7E5252527DFF7D52 %77F87D527D7D7D522852A8A8A17DA77DA152527DA17DA77DFFA86085608B %8484608B60857EFFFFFFA1C49BC49BC4A1CB77C49BC49BA2FFA29BC49BC4 %9BA8A1C49BA1FFA782AD828982A7AE8382AD828982ADA7A882AD828982A7 %AE8382AD828982ADA7A882AD82898283AE8382AD8283FF7D53775377537E %7D77537753FF77775377537753CB537753777DA8A0C3A0C2A0C3A0C2A0C3 %A0C2A1A959595927A8A85959842784A87DA85252A17776527D7D52777D76 %A25227527752A276527DA84C7D527D527D527C7C5152AE527D7C52587C52 %A8525851832D517D5851585852525251A87D527C582D7C58AE7C7D528352 %52527DFD04527D7D5277775227774C52284C4C7DA8774C52277DF852527C %527C5252767C4B7676CFFF2E597D287DA8527D7D527DA85252A227777D27 %7DA2A8527D2777A252277752767777527D7D52527D5252582758A7277D7D %2758A7527DAE27FD055258FD04527D7C527CAE525283527CF82D5252277C %7C27837C525252272777275252524C7D52275252275227525252277D2752 %2752527CA72752F82776524B76A1CFAFAFA8FFA97D53FFA9AFA87E527D52 %7DA8CBA8CBA87D7D7DA8CBA8CB7D7D7DCBA8CBA8CB7D7DA8CB7D7D52CFA8 %CFA8CF7D7D83CFA8CFA8CFA87D52CFA8CFA8CF7D7D83CFA8CFA8CFA87D52 %CFA8CFA8CF7D7D7DCFA8CF7D7D7DA8A8A9A8A8527DA8A8A8A27D7DA8A9A8 %A8A8A27D7EA8A9A87D52CAA8CAA8A17DCAA8CAA8CAA8FD06FF7DA8FD04FF %A8A87DA8A8FD05FFA1A8CAFD04FFA87DFD06FFA87DFFFFFF7DA8FD05FFA8 %7DFD07FF7DA8FD05FFA87DFD07FF7DA8FD05FFA87DFD05FF7DA8FD05FFA8 %7DFFFFFFA8A8A8FD05FFA8A8A8FFFFFF7DA8FD0EFFF8FFFFFF7D7D7D277D %7D5252A87DA8FD0AFF27A8FFFFFF7D7DA852527D7D527D27A87DFD08FF52 %FD04FF527D5252277DFD0FFF2727FFFFA8FD047D527D7DFD0DFF27A8FFFF %7D277D527D7D527D7D7D5252FD0AFFA827A8FFFF52277D7DF87D527D2727 %7DFD0AFF527DFFFF7D2752522752277DA8277D52FD09FF277DFFFFFF5227 %52527D52FD0FFF2752FFFF52272752A8A852A8FD0DFF27A8FFFF7D527D27 %5227FD04527D52FD32FFA8A8FD0EFF7DFD19FF7DFD04FFA8FD12FF7DFFFF %FF7DA8FD11FFF8A8FFFF7D7D7D527D7D527DA8A87D7D527D7DA8FD05FF27 %27FFFFFF7D7DA87D52FF7D7D527D7DFD09FF27277DFFFF7D7D7D52527DFF %7D527D527DA87D7D52FD05FFF852FFFF7D7D527D7D7D5252A87D527D52FD %23FFF87DFFFF7DF8A852F8FD04527DFD04527DA8FD05FF2727FFFF7D2727 %5252A87D7D527D527DFD09FF7DF87DFFFF27272752527D527DF87D525227 %7D5252FD05FF2727FFFF525227527D27522752527D527DFD31FFA8FD17FF %A8FD10FFA8FD18FF7DFD48FFF87DFFFFFF277D7D7D52527D52277DA8FD09 %FF5227FFFFFF527D525227FD067D5252527D7D527D52A8A827A8FFFF7D7D %52527D525252A87D525252A8527D5252527D7DFD34FF27A8FFFFA852F87D %7D52527D7D7D527DFD09FF7D27FFFFFF522752527D527D7D277D7D277D52 %7D527DF87DFFFF27FFFFFF527D277DFD05527D7D527DFF7DF8277D52F87D %FD41FFA8FD10FF7DFD05FF7DFD12FFA8FD48FF277DFFFFFF527DA87D52FF %527D527D52A87DFD07FF2752FFFFFF7D7D7D52527D7D52FD057DFD08FF27 %FFFFFF7D7D527D7D7D5252A87D527D52FF7D7D7D5252FF7DFD34FF27FFFF %FF7D27275252A87D7DF852525227A8FD07FF27FD04FF272727FD0552277D %5252F8FD08FF27FFFFFF525227525227522752527D527DFF525252F82752 %7DFD52FFA8FD18FFA8FD48FF27A8FFFFFF527D7D7D5252A87D7D7D527DFD %08FF2727FFFFFF527D5252277DA87D7D52527D7DFD55FF5227FFFFA85252 %F8FD05527D7D27FD09FF2752FFFFFF522752527D52527D7D527D5252FD59 %FF7DFD19FF7DFD61FF2727FFFFFF27A87D7D527D52A87D527DA8A8FD07FF %27A8FFFFFF7D7D7D527D7D527DA8FD047DA87D7D7D5252A8A8FD4DFF2752 %FFFF7D7DF87D527D52525227525227A8FD07FF7D52FFFFFF2727277D5227 %527D277D277D7DA85227275227527DFD6BFFA8FD61FF27FD04FF527D7D52 %52FD0FFF5252FFFFFF527D52527D7D52FD057D527D7DA87D277DA87DFD4D %FF52FFFFFFA827527DA852FD0FFF7D7DFFFFFF5227527D52275252527D52 %7DA87D7D5227F85227A8FD51FF7DFD19FF7DFDFCFFFD4AFFFF %%EndData endstream endobj 12 0 obj << /Filter [ /FlateDecode ] /Length 13 0 R >> stream
+H‰ìWÛnÛHý‚þîC
+)–N§åSÞÇÕ`Y}­„@æ¤Í9ÿ0_m;¦ÍÇ®•‘Ê“2Ç-ï¤;B·6¬å'S”†ª-…@$×ÇsöÄz>(o`:|jÇ!4^H4_\ÇJù\®Rs£Æo–r½™n”Ñ­2º]ø›ö}«,•ÅãÒÍ£š*S}_ÑÅjEÄžm:¡w¼#¤ÛÈ¡Ž´:Ñ„~›ëÆ}ÍÆ篑ÏtðŽ<ä²òz?¯z¼lŽïg-ËTÞlZÑ2]ë—íÇi?ò‘žÑ“UêUóðoõÔÅéÆC7rÑ­üɈ½Ï™nhŽi1HOþåQ½è½~БÍѽ˜ çü(?Z¾¥K£a…Äø—‘Õ¸ñiÕlQb†çEá²’xï µ†fÅ ±BKÔ/ÔÏ¡buYm·ûÞË¶7¨®<â=÷aDo÷|Ç bEE1µÈ°¢€²|×QD!ÍxÚ¶\]
+ã%a}ÿ¼jë5UßÇFÒ¨fAæýµx›†íyiìôþñ¸$gGáÉ6hƒüDLÓ´
+ÇqÏñ
+·ðL?‚aïyž^D^ $@êe@î^!òã["|Äÿöâ
+b R 2äA`
+Í™âË×\'MRA$C¶dÇÓÌD’•T1¢Ù\x’†PS Ò_ÈÔÛ:íH:ÑOd¶sh[&ÙÓ VÉMUbuR™P_çRå1‹ò¨ ± ÉÛ±}y±.Cä9Ž“8³8 œ +±¡VRò“
+íªêuÖ®í%Ú5*WV¯³v=R.óH¹H²Ä \áS”KÍWáä~<yPŽEéZÎxRîÇ”.¨ˆcR¹Ì£5ªÜ+]\]^9§ÑXîG–!´ˆ.µœ³h-÷ƒË]Ä3I´æÐûñ¥ 0o!ôÿ#„þBñãµD߯ î^üx.„<ˆ!¢³£‰!w‚ˆ3ø§E‘>oHo &fN@J”-Αã÷~±Ô¡®B‡ê2GJiO1m)¤5ùä’M&iˆ¾‚‹É4í ›VD³4½Gó[ Æ0( …ܧ˜0$Ã04>: 2˜¤5c²¦-aÊ"¦-`ê<¦Ïb 5ÆQa,å6…„1×€±õ_‹1Ög…±–akÂœGÌ{ÀÜ{Ì¿…hè‚.Èuò B!Âá! B¢!(
+Â"ýä„&Bp„Ç;' Bj´@•f¨Ód”*B±”ËCÁ,¤LCÒ¤M"&H]„äHŸ‡ZH¡†$*H£4“Ð Z¡™Úé¡¡Zª¡©
+Ú*õÝ!¸„w…
+?!QìPü ʿ¼ÞÀXà ‹˜vXÆëXa!Vb`) ¬eFóN”ÅQ*ÌQÀ‚J½|5t-ˆöžZ{çÖ;6ñí•/ÖªæªUÍßìô÷n§ÔigËüäë7ù¡ˆ‡¹àøõ?ky°Eg…½MÊãg3¼{ÇÓ{¥+vÔ•cµÆ8vÎYçøÔ‚ABÐ-âŒIfÊ)zÔV‰¶D.ºÀ4ØWà Ö†P€ôD1coH-’Ö#Ü.U°茼fØWàÖ‚P°Ć½ X^bEÎh Ô¨z- ºÀØGx[ÈþWäñB¶§«•ì#µúKˆí°í‰‡«à+pµ4~Îøh}ij·<Zd/ðºJ?sý>>ð×5þ_þÿ|̳ôÂC_)Jäÿq¢8'ŽÜûIÙ㛯|ÿ—ŸÞ½ÿùÝûŠ¯?¹<…÷×Ù$üðíwÿþãÓßÿõîçïŸþŠ—¿ýáäêUœ­¥“”ÒžbÚRHkòÉ%›LÒiI
+þ(±×%ˆ^„øˆ ‡Zˆ¢†8*ˆ¤ÄÖ— šâ ¢>:E5PÖ
+;Ci§-Au#Ô7@…=ÔØB–5äYA¦%VÆÙŽï
+F#±&O„‘wNÀ• ÜiKÍp«É&8W„ƒ8™‡£YX›†Å)XÄ*›`}`…–ha©`•ÒLB'xg„‡x©‡§Zx«†Ç*x­ÔÓ’`¾&`ƦlaÎ&­`Ör™T‚yG˜x€™{„ t“Ã/púŽ?!«ìH
+Q¨J¹Nž£ŸåbQ¹´àœ¨¸j3§É‰k—«9Œ®áÊÕq%-ÇZÍõÌ9£2‚óÎŪ—xåìíJ¡ gõ…Ë­8Qãbó*ÑD?m,x*ŽsÑOφhÃÑG? ýˆ”!)3ÒO‰Ý ô£ÒK?.Çá‘©#†¡éǦœ~t6fŒçs
+ŽhÔ~&Á2?‹àˆ#}'8ÌXÛ#çÚzÝ5Xím-ØDÑVØŒí„0`à{È];À “àEг똗OÏ]²U V}æäx
+Q&5ì±`cÆÊðe\©Å,·5µ ÊôÒüJæ%Qãå9Ù^€ñZ|+°‚&Ÿ,°ºÓü–ZRL’=.uâá¤ñ¤Í#JCšÇ´*FUȶimóʇGN¹†;ÁPyÔì'd
+‚³ÎÌyGrÏ&ÆÎÉ'gJ?”rʨ¦ šƒZ…¤! ñáYÆn¸è‹Ì
+«àD]OC€Ä"J_JïU£(Ñ
+e:-})½wá¼@ EÁŽ=ÒªôÆsKy¹ž•Kß¹h-`¥Ô‰s(\RȤ°I²DS)ˆr!™ìJ´fŠ]£wcbS*™Ò\îdþry—i³LØÚ¸Ú™%Éü(æÇ03™—Àœ€BÆœiÏO¿Õ'秞ùy~\ËZ’1ñtÖÙÔ<•N°a¬<4Œ‰‡Qò. Œ&×"Ñxˆq€Î‘pî6Áu%:@Q-@©A"rHF+¤€¤‘˜:“&`Æ-àÜ WQvõEµÙ=Õ=Ñ=ÍG’ Å…að+÷ôÉí©mÄ2¯=«IHí)í =ÐÙs90iEGdOã‘ÄžÁž?”E1¨b¯‰E+Ùõ£L•—c$RcfZšXU«]ˆ«©Î_` „ë)2Vñ;@45ì1Ô¢•#ΉS–E\8J÷¸ýÃíW¿Ûâg/¿š±qLQÒsÍȾ…ßr—–]ù}\ù²ÜÖÅöÑRpg-à³®õœ‚¹ð%nçs…ÉL9ËÜNÑï(¿¢Œ'…®ûç¸:Åeï_ãE—xþ–á ÇÐäyÕs¼îY^ù ¯}šW¿…×?Å+àÌk !—91vFŽ­H,‚÷ÃÀ;âÊ{¢ç]Ññ¾hyg4¼7jÞÞï3ï‘ži²a1;òÎÞÙ¥7ð+Ã3Öæ2\RÍe]¸´ŠË;s‰¥ :s‡&ÆÎÈQ›c3G ªÿÊ=àKWº.÷–œK࢛Ãmo ÍAÕµ¡,±,X ¥dG–w& ®èf/Frr ú»C…7^hVºêl)Ä¢bDªÂÁh$&›€ŠN‹@ o‡Ê;K—¡º
+p¯Ê†—º½¬oyq« [^Ö–²¥ñŽFû9§<÷Œ(Í¢K“äæ˜JSp3”.ð¥ö¦Ô\•JOmy"ó•â#ö›€8íyW2¼)^„&^€òò³–µÇ´•'/<eÝÉ»Žè—^öv.Vh…2­H²hkű­03ÕDpQrIÖVÝJ1µ2„V*@f_6æ·Êº`Ò3ås£;6ªó¬ÇSã74nMã¬f‘¹¹È•×ìž‹²Ÿ­—nwöÄl Wp×V{vdÛÜäŒ+Ÿ×c¨8gլ錓úÞdòbújB‰ž¦¯$ÂýÊáÄT=É'==A«éÞ¿‰?|ó¢{Ãð™Ê8ߘi¡Û_fºž’ïsú¿Ô—_Û¸Å?Áý~ ¼¢(Šb°( Jbצ}Ú‡`¶3h½E’¢_¿‡—ÔÛ%Ûê&ëØYæØÏïœ;.¹~*¯X „â´LÿPá§ñ@)Ó´<!˃ž,»ñü-Ÿ¶”›?-Nõ+‚ø,†w_æÿ-¤ÑXÂ÷lÿ ÿ ¹‰?èøæõ3Ãwqü'ÓÆ¿^i%ýiªŠW(Ÿ|•µ3±žméMýQ}îNϾ>=¿¾œ¾‡öåËë©?ôþðö¯O¯§ß~ÿï;žá‹–ûŒ-ö 9ã*P¼t
+ •È&óº®ëÚ®élWwi »0.‰9f¼,ÅKº˜)^Ò=ÀLñ’îfŠ—t0S¥Ö[áLCMÔBºì…ÀLvÄï’{jÏóínÑâ t§hÏÅÎ\
+‚E{-4¿`õ¸èê
+pÔ€¤A»´ f z:P€¦’€U¼j`Ö
+‚D0(„FP†Ep´ØËÅQ P$‚E!`4‚Æ ¡[$O‹ýìP7I¢I!¢4¢Ê ²,¢«Å®v(ª‘&m
+§u‘g}-v¶C½˜!$²Q!#5²Ò 3-!<[lo‡V,ªáª²šÍW³_üÞw¼asÞcŠ·…¿Âø`® {¾önÓLìÎÚ“.Dè9œSìLë’ p~P— ¦"b“èæ·¤ ¢µnUX°ºWÌr_ýÔ ^öÓ϶ _×GƧ¹é‰Ÿæ¦'zd|š›žè‘ñinz¢GƧ¹é‰Ÿ2sd¤˜ÁQ‹W[œåD†wä¼ÉïjZ´TµîmZ´TµîmZ´TµîmZ´TµîmZ´TµÖ›VhR×1¼–ô«=e½­U´zMÿdWÔ,‹â“vEÝŠ†ÉŠG®lE 7žå+ºŽ.ñÖ8ßµ–<Pœ.usáKuÅöÑ wßhæ˜Û¦ ËU¤Ek'¬ê²Ä?2ÌNÿ¯Ie#ÒöÎnÙÉN¹ÂI—;XÞ¡^Ñ´_íQ¯hÚ¯ö¨W4íW{Ô+šö«=êMûÕcõ
+—ZûíȃïÑ©G§ÿŠ#Y¦«^¨ª(KÅ!;ˆƒÂ]–ùÓ>Ñ›Ïk§Ùo~m]ªÉ)”ý)Õd¡äYaÌø…Xð¿R²?£œ®“:+¬“+Ågüñ;÷gÓuRgñ:áËÆÊ¿`F>ýRg…u„àŸ°Ìª‹3¦Ë$NÂ*¶¥7õGõ¹;=øúôüúrúØ—/¯§þÐûÃÛ¿}ýý߯ÿxúçáÓNß^¾¿£«#XPì³ß @Ò˜k“ÏŒ7éÊžSƒN-:št´é8õN­)š5ص7,[v0m°m0n°®7o°o0°·p0±#ø88Ù{Ù»9øÙ;ÚL~hò¶öÆöÖöæ>,`poqoò–î­üîoØõÞ÷š½_²ÿ3 ÜdT5s'¡G|÷—pu¢¥³—XÆÛ$·‰xz¸M*%J¼XÞ#š=¬ïñÝåð:¯[Ó~±DÌ놚Ãø&%›ßuUÌoÔŸB+óÌ­Ÿ3KîA†§·†,Ç3c
+ø·DXVÍáÙ D;áð!‚Uˆ
+€Ð\ƒàµyãS¸Ë¾•@4K8V!ª5"Û€yÛ¾E”;üH&–0¶‚Õ5¬o
+ ©œøé”Ã% “(h¥A.†YlÐ\sؤ“À‰Ò48h€D«ßd:í°Å¸)A’jÕ€­ŒmÁZ‡¬ ¯™Ȩ]>[ º²¦]„˺ð]«m+¾é¿«Æ] œ`5òÁ ',ò¢En¸éHXEéAe”TDɨ|ˆÂ•#Ç3h‘ú[3ÈFÕƒLT…_ŠZ”¶ŒRƒ"ÊQc{åQbP„ŠÛ U7ÌýÇo6ªd¢ªA=IF Ô!Ù¶çññˆ{
+çœåfÔͨ½ÒYZP|bgTÏÈ\©:ñƒžQ9#5£â\Äå÷ZùŒænW”!tèku3jgtÉ–À£KÕ323'6ï2.6cµñ妯7rZq8O+]ÜæӦ㻷âºs^xÎ+ÏyééÎKÏuç¡IåËN_sB¤8‚ºh¶†‡!?Õ<ù¡ÈEšG£’ü‡‰•Iò””Ç œñ´ä";ZšüØdytòÓŸ*¡üåÇ(ˆ¸uIž§rž©ÄÄ/½#ÆM?nëqßö;3î¿~· ûi²aú-1^õñªöa¯x>C„exoŽïUìf4¦Ù>aFcšíf4¦Ù>aFcšÝf\¡s.´¾k¢èUàv š7`|×8\PÍ&a…ÑHƒÄ±ˆ€éäpÑ6¤„YbO# Í9¢9!jNÏÅ ÷pÏ’sØâF<†] aØ8ÓW?{õ“Wœ»(Ž]aæ*‡yK ³V笊A¤â|f«0VÙ8Py¶(b¨x d ’–R364Ó¢`J¦CÇT°LƒŠ) ØüÞø¾%vzÍ×ìì‚ +N„’¡£¢£‚§ Ѳ1Ûà-vWðWΆ.ØáeðE«ÕŒ„†ÑEËeŒœ‘RDë…&Y1ƒj&R=ØyfÉŒay4cÁ+™y3°Ž¶l˜‘g&ÓS0Ke´h0©·iEìÔš©Ü0¥»X?3¦xÎT/¢qƒu+Žšc¡‰î|lx;9x9¸¹äÜ£y;úWÊÙ1W¸ÓÕ!;ˆƒÂ]–ùW?Ñ›Ï ¯Úoü>­â^‘êˆO”>‚TÅtÔi~¥#ÈVà”L˜FHÝÿ3¥Ž¥¨†ÅÖÏäõäBàµøjQM!' -ŸÂ+T|¼ÿÆæèw×øæÙWñ>ÛÒ›ú£úÜž?|}z~}9}ìË—×SèýáíßO§§½<¾ÄCïèúÖ/ö—èrÆUN»—·ÀnÊ‘vY纕ºélWsy×\Ô .å‚ÛwÇMÛr§®¸>+®Ê9ùVì{0W߀F#ÑöDÛh4m ÑH´e q+i¹ÔÜB4·‚K‡à²ÑqÉÀÏIÜ-4wŠÂ$oÕ#~—ÜCQí+ŠTÝM´çbç ê}D{-4¿`õ¸èê
+&IÂ, P•—%ƃÀ)†Íf†õnŠU·Å¹ù¹ ó—þƒÿÆx˜B0(¹¶È¹Gî.—àBu­‰¦S.úZM§\ôµ&šN¹èkM4rÑךh:増Mt_²ÇkÉÄ-ZÐeë»ä¾Måÿ ° p§A»/~픾Ž±X\'>4|ã 86$±½žkÒnj6WØáC\lãç&
+{2õÓ¼­^úk1ZZ°ˆƒŽ0Õ$V{Ì…88Û¤–A‹K–›Ü0O±ŽCD¨%ìó Æ Êç [ŒA”ÏA·ƒ(Ÿƒn1Q>Ýb ¢|Z7!ÕMGÇÉfº¬À¤}¥ÃÆUà‚RØ_Q°¢,jüa¬[ö…ž––ÙŸ]ìFÖÙ’RËîÀ*ݯSY´å¥}D¦»›âÖ"Þ´®FËdqqi‘×u\&âý±÷ñþ¥ê—‰<ââұߊý;tû»ßžoßܪ>N—$S+ò03‹Åzz0Ÿë§v»ùôãyóö²ýÕŸ°/¯oÛý©Å»¯Ûíó÷—Mñº;U”ïiê$bËÂn@”{S¸7…{Sø/4TºŠ¯#i§Æ2Žv%"+ÜÁàNwlË
+µjºÑµŸÖŽõmI®•gYOôLåÿ ° p§A»/~픾Ž±8DžøÐð/ :DÌ)|Ìy ³–ÙóA Ü¥ ™‹þЉ Whâ\8}ÞòêÑ7/ÅmòÊÿ¹Öœp²—[ßôÓÿ¥¶z9Ý\U]ã0à™²±VPr,(Ùù’w&ú€êZÄ’=Vçuø§ŠÔn‡-`ß^o0÷äcÝbîÉǺÅÜ“=t‹¹'{èsO>öк¹§o±W«ù ?sáj`;àßp£6;Öèk}ñL›ëÔsnó:‹xb舃GÚÏ : ÑúÃêÈ“€ÊP÷gj:Gßjj[¢ÆjÝ æj¿Å»2h‡&hƒ„F(ˆ…h8ˆG‹÷eQ ‘QSY‚òx(P jÄ¡Jê$¡R
+je ZêÕâÍäŒCÖäMBæäÎ@öä¯ÅÛ3È!—Akhd­ÔÐL íôÐÐÐ0è)‡®
+è«„Î*®ð:p‹}`dah ¡Vl£,A½=x4ƒ¢s(»€ÂK(½‚â(¿Chu0 -£5´‰V¡Ð2 Z‡C iM° -…ÛŠÐ_jô™ýF£ïXôVÇГ8z“@’èU
+MË y94±ÖÏÐÔ8š›@““hv
+MÏxKè€ô-CWäèŽ]R¢[ªXb6VE‰\Fî‰H—&f\/ËzÔ1hêälÖ®ø‘œÍ_¾û€õõ@nÙ#c•N
+´+¤_‚tà [c¶hÊm­1[4å¶Ö˜-šr[kÌM¹­5f‹¦ÜÖ³E±*›HѹŸHì ïj}¡&I¿ø¿!àJÑX#üzÙËü*£wsçøÞìC· õ§&ƒþ­ke©
+ˆdñPVMÁŠ²¨Xô…žæÙŸ]”ªE·N˃<3·*ÆѼ®»èªXìî—‰,ØòÒ>"Ó*.SñÖÐØ¢ïý2™G\\ÚGld¼#ä3½LZ¦òˆ‹KûÝÃ-³eq—ú`j¶}³ËÉzz0Ÿë§v»ùôãyóö²ýÕŸ°/¯oÛý©Å»¯Ûíó÷—Mñº;U”ü=MžÅêÂn@È…ˆÂÞ Rçó Z‹C‹iá%±ïh;x¢SÁä\@'Gƒ£á Χ¦ƒa> Ñ€0=" ÃŒM ½6Ž_kõ¨CG¯sü2 :4ñW :ñeáç:„  å
+è ¥æ¨ë¾!¬wIãÛÑî~Gwƒ4IÝh0MmµÓ’Pè%Ô£2ÂÔFšÆ(£1Ö8ã!¯Á2²¥å¶²ÂÖVÚo¢­±Ö:ë¡ÅÁ1W:î*'Ф%öA9팳Î9ÙžùÒs(šðµ—¾ñŠ¼öÆ[ï¼ï|]ôp<ú´:Z1í–Žª®©sHHs—£€wxì€;wV0„yß”ø˜™8™œóž•;^v¾)Q3Yè=7vNò31tÏÑ–&h:53Ö‹Î)òu°Ø‰²‰@‰B‰D{%"%*E2T½Ó!ÊLùôÕN½ŠîZ¥ö–ÿäàÂ:uj&å©yè¢×§TU»”ˆ¡íR#‡ôì­í>IƒÁyJwp¹4èFV÷ e{ÙKL™4Û!g´KY^õãº?¬üéÚªŸ2µWÿõ«è¾g÷=»ïÙ¿¸g;mþHsúâ6MÎÅ”Üãº|›á^ .I,㘇ZüÜ"PƒÁKLH-ngqcGxG*15¤Î#…©ì>
+‰•„ wC_3^v¾D…8ªz
+Ÿ€wð6¿’ø{„Ü`d“MnŸWU”,Û²»=3È"h}°[nÑ´T¬zõøð¯€ü§üº›™ìåøÂ$Nð‡íÉó´«™fœÝ¾ñ.f>Lð|Ï€Ïáe.ÆáÒ®ß6€¿.Ç×Ã~ìÙõÛcÅÓ=V¤=–“=–žÌÍkož&™lÐfM5Ít«­±Ö:ëm€¹ZNÑ&›m±Õ6ÛvX6„Màf
+N¼ ¹AòƒËî #ûÂÄÞ0³?,ì…¶ÓoQC4¾BùʯPþoCyhRær|±ˆ$þô*7‡û+.L!:{ñÏ¿|ò¬½Ø0ÙUÇ—-fóCm&œu™ð ÎJ“ùUõÞ[¯½ôÜS}Eðf¬¬ï®Ûnºn½µV[i¹¥á¬bh¾¹f›iºöÚj­¥æšj¬«ÂºÌ5 Ÿ\µÕT]zi¥Âže´ã´À¤Ø4£f`Õ:ÌZe»–`ØV¶lM›%Û¦RgçV†w[îÍžø7volÞœ7¶m• YµU±C lÇÈzi$LC2¤WBº­H¾©è‘™Yª‘³ Ù[Ç 9½"GgdºGÎ[d¿V(…†’(¨Ž„:Y‘È3jÇ£†,ªIûŽÚª¨²Œj‹¨»µP‰i¼vÝ5jAÁ&îªP3*У¦-j[ÛŽJ¯¨øŒÊP€J Ê`¬6JQ¡Úá”e0Â`1Zi,¤ÆZhØ`r²¨À¥ŽŸÖø¶¦bÑ{± =î<Ðõ?HG#Ÿö«ûçKÕó\ äˆü–¬Zãf-nÚãæg<*”ñ`ØÆÓZ<µÇÓψŠhˆšžj©:ˆéQNïÅt—RVRÑQQÒMG‡Šª!¢fˆ¨Hè²Ëg!ù$ídå´¬šUS3±VÖJÒI£6‰dmÜT1±Švöä©ìx£xó±`3BÛ’ÄEkŽœ`7àΠÃ`f–¸ý8HL&‡¼pØ…6èÄöKf;,ã~ÀQ2ÜA¯–°í~"“yPuÐ]°ãñ”?j·Ã üNÌ;ËÎJv× íÈÐ
+-鈆¶8hL€Ö,t„ödhP…uÄÉ@›4*@«hV„vehX…–uDÐ(<Œ…ÆyhÝ Í[¡} X …-wÄÖPNB%=’f†j®POÌ
+……¬ð†bE+#Èšˆ“Àú¢ÍX)4½<n˜æ¢£’å”nOÆ+ôLöDäpÀÊ 9¡¯bÉ?/ÝÍÐ;/"„E(Œ<6ÝŸL*¶Íp¾Šäˆèìðˆôˆø°‰cº‘ >ŽŸýhânňåHíÞõèAŽâAŽDŒÌ.Fa").õnýܦ–ô'}ažèf/q¢¸»Õéu±cžlZv³Nz™ãÅ»‰ÒÿàÉ>ȳ­cÝâ„®ë/‹™Hô®S½%ušÃvý¦õž]z§ÏþþÇ_~ûûÒþù†özø@³, íŽzC¹#ß‘6Ô~XO¸7ï {Ðjœ„SüÜS¬êö)æ%úu=½â)êp^ߦ<¢Îþù„üÔç†H¯Q x—ÏLßA½7ükÂÿdÂ߶]l"•wíYw- »$Ø­ŽGnõ’÷|€PìfÞêÊf×±q ¡yÓÛ¹ÒèËAæƒlÙ2&dQ¨qÆ+ôfêíboÈèÐvL””~^f¡#3‰AC,Ü7aÿÚð¢xE _Lƒx½aŒÙ•I–ºÎHæÍr¤AdVBámÌLøÁæUìÎæÊeW Óµ ”jÔXÝ);û ¤ã¬AñŸù†pƒ¿ÁÝaïQÉ< yìbÌI2*þS_R>$_QǤ÷Qãä§êé•õûPßûÅWþ;
+H‰ìWÙŠ%Çý‚ú‡z°Á*r_ŒÔêM–Åc0C{¦5KÝ#fAÈ_ï“‘Y[Ó‹º-?Ø7šÛq3##c9bx:jžR)ü¬ù´î²§¦>š§¡F觥U¡zj¸AÞEÍÝKF?ƒÂG~šÇ*ø¿ÂÿI… –e™–qé—¸øÅ.fQ‹œ—yžÇy˜ãf7ÛYÏjÓ2MÓ8õSœüä&;éIMb\ÆyÇaŒcýh›ÑŒz”£–aÆaú! ~pƒô 9ˆ~î§~쇾ïCï{×ÛÞôª—½ˆKœãÇØÇõÑEÛDuTQF–0…1 ¡
+2¿øÙOFøÁ÷>úà½wÞzãµW^zá…[ÜܸÉnp8ØEœwÎYqÚ)'AÂ.v¶¸¡í`až6Xh²ô1V[’„ÔKcfÐdps3ÜÅD³Œ7Ði°Ú£A
+$,ë4k8PÃW~Ñ=×ÔAÃØFÃCÒ.>
+$Ay#…ˆ(ø_ .Tð“
+D¸±rD0´Qt¸"=*}$QFÔ…h&šˆF¢¨/‰B¦Fz"WÈ2…t¡rŽ\?+æ-•f17é 4mh¬4ì¨ßP<P`jʃ?Ðý;õ©•nÙ»åƒ:ß÷•§ˆ§SõŸRøY#Ûg/†w@2 S8‘?;‘=R3›é©É ¦†€¤{šOtþŒLÛŒŽ;pœï7‚c“±±Bã
+Œ ‹ 3º
+…2Á`ÁÀ!ã_Cð—¡`¯@ÞH`—ÎÂiÂ6A˜6šõ„b ¿,.‹¬Yƒ\Ý0Tê‰"Q òDŽšT"Ø•°v"À$„Ըкt¯¤/u°aDWBK,õ2¸bÄ…¬¦ÒJ£4ÓÙ©½¥A©É¡Í5èsv0Ôë¨Û¯êxÜóú>RßãÎg€ýŠ0–û_ê€õÀ˜Þ\8ЛPS([ÈòDÿS'艒!Éútù”¢è„Q  S?ÐÔeÇÔv M@€RG¨?M”ë ŸD0ÓzƒÐ¦îæÑç’i=âž5! ÂOiU¥©ÿY¤ŠGÊàHž>P_˜6
+nÞ°)À¯ñ [Ñ ¥m›¥ëʵÊvÁ(ŸVý¹yöâŽUÃûFtQjE6:¬ÉÖŠÖÄΠ”YÏ«HO@—À
+i,¯p²³:ØÖ™pìWew/%Ö)ÎLWžõ¢SÊl´Ý¾,ijÅj³³rÝ~ÓoØ3LͳþwöÅ|ýê7ï.^½¹¼þÃåë7×,úuû‹¿þã͇Ë_µÃw/¿ýe³g¡Îy…p}Ú,Ë2c Ñâû%.añ({»¼°)¼$‰y™gŒgã<Ì=Í™ž&GC£ ¤Én¦Ym ‘$44‡Xš?Ò䑦Ž¹Œ= ž†Œ<`H.æ2Xôe¨pužÀ4Ñ”a"yÈcD"x„àâÎ1±ylS8‚`óئpì Íc›Â±'4÷h
+‚K˜R¿íSr#?¦°¸1yÅ­i Á‹/Þ^ùîÍõ‡7ׯ?ùd“ÍÛš/¾O¿èüË—>\¾»Fš÷ÿúøî²}Žï‘å[®U
+µG_6”/Øö‘ì“ů~$ö÷xü'„?´¦ýcûõßDûŠä_=Ç¿UÏs¤ðspUóÊÑ‚ÏwûwM
+Ÿgв.ž†¬øC‚ÄÎÛö<Á*¯˜3àlp­V1"¶$$Íôô2mg£Ì³ÙcX[Z• Dš–wxÇÊè)-ãsH@yÓj\Z÷ž ¬Þ!Ž4oÍ­Ö-2ÞP²­¤acë ßù„¾xÓºxg+}U[µØÛº­îäÃëxÙÚA~{s×|¨.ãsH½)6æVuïwÉuÅG•6ín½:Dœ|U6í-!Žãʛֈ×dà£H@»yÓjßÎâš Äql«ÅÕࣽ¼•wì£@ÚÝ÷×ßy†,àM«q;sk>äÅboîšlu'¾ÚÇ›ö™™Ý-öoâ¯îäMëb¶x Âs]ŠÊîâkn†“»v–ìK=lR‚–Ô”à£H@_¼i]¼³¸¦qàjq5øh/›[bˆ}µo︿þÎ3doZÛ™[S"/{s×2Z- [ˆØâÊ>9÷6)±¯%>ju;oZíc‹ÿÒ̹3¢•–¾xc§/®Þ~ü滋חh”†:º×øÛuGuÏî˜v_ÑèHŽTñ“¤Yþçø'êŽm©RĸC¯è€ßžÛ3i¾Ú¨©z×£êéßr YéãõÙüO‘r —«ÎÂf…Æu>huàøŒuçz’ !]AŠÒI¦“„´>&q¸Tì¢Â™ÎKX;Ìv¦U]R3‡ðb
+'v“Ì,ã„W¨ý”I'~S1UVJ
+:ðJdOü¦"YTJ–Ã[ê™YI©°–þ
+QÉ{*Á8cH o2ì¤?0cEa’êTîð^;óÁ¡²ÓXT-"šø-f°¬x gâ­Wø­§«,ÙáL0'–¢™3€e9â.e‹ô(Ú¿Éš*£Ì¢5Þš3¿IÎ**ùk,݉ßÖ@••:1 Èß¿©5–UÌ@+S!žù-ˆ°¬Ô} .ÃBa †Øˆ”°µë0‹ÅFI ™Îz=ìˆ,óÎuNÂfB9–©.JœgmRAyèF.–=,n •®#R3;²¸eÙã,Ã#MºÒ)!׉Ç'E>¥ÊNóÉ€¨¼?²Úw*0b³LcZB²
+íd&¤lzö¡}ýÿðý¦¦þËL/þìIëÏ´Ÿ‡lˆ<>Yé@Y?íºŸ' ìïûîÙ¨g[dîÐ!·l Ÿ.ðéi#’Û6`"ãÅ6êG÷üÜù×¼Ð=Òt|kzö?ÓÎ^žããåéîìt7ýt~Ñ»†NÍ ÁÚMôR»‚‰ÐÏ–Y¼Í'Æ{aq»‡Äª*—ÁÑéÆ“ë¶49fúQDÞôT¡R=£©MØÆs076 w¡…g
+ÞÏçÝÒC—Òá1w:BZË¡(]åân+hC«ƒùh*C¥(4x$ä¹´Ôm°!ôƒ QO™äF¸ÑÅÚQ\ÚWSü‘áêY2¶õnŽÁôxßêÛ̱ц½‡ýÙÆ#ï£rSèŠmÊ'SööhG&ÙÞ­C’ËÆ÷Ó•œÚ^k繈nvY¾Ì {¾í ‘Z1¸âý¸I†ª¾eåªíôs¬y—iÛ ¬D°µü<o|Á×؆?÷ŒÆë>ŠeIò· µ4ÿNêÖzµ¢íÍ1'®@kÁ±Wµ…íÛ£›
+oh¥LèÕuIâv€Ö(ö;´[X›Ýc_cYŒ5 ]`Zϼž^ýnÿøÃåéöü%µøþÍê&¶>kn’…-ÿOvÚAÕàð?ÊÿCsÞØRZ/qþ3MbYôú7+ƒ”­JQüïÅÌçÅ~á…„aeáÚ|íÿeÄúÍþˆ«vïí=U9-k"ܱ}“R–‚¼ÑÜpÆÁ\¨íæMdRA°q¥ñfv»›ï¾†Ð#Ò÷_½Ý=†Ž»‚”»}›áÆšñ‡£G¿Ò/…¹wùî·«ãGgï'ù‚é{ë⟽?zw4KÝ:‡ƒnç
+‡Ÿl¢:¼´Ì{ïød[ô‹,û¯#}ë®çGü}½0Cm'GqZ)  ,ÖGqY‘“ÜÌ᱓¼½v1qrDÑ^‘ßÇ·- ±\Ó^6ß»w÷Å‹wÛ'o¯Nií ‰aœÚ
+R§îMˆ+¾š¨”„®Q8õ[ÕäXÀØÙµ°2g7ÑÑïùèrìቚ”€¶V7€ziWumòY &^‡ñ¿&$e¤ÄÓžá;sLÙ
+48í$Ñ 5ž¬š8d+ˆ°­}¦Äöâ`8t8ti!ÁžïÕ•,`´NÎ|PÂG0Nq@íLb5s‹
+y“D¡’ðáí2©—‹íûCçMMÒ!à(9¡s¨Ùv…Åp®Q|¹TåT!z+!¯ÅKÁKÛSlVmb程Y¸ˆ
+G+­'oLöbÂYiÁ¹ËCÇ£X늖O(^Ccb½é´«Yb¸Ô t!´þr_kQ(I–|]„.Ã*”•ƒ•qÍ^g¼ñ"–D:,I‹Q:&Wõ™Põ=@ÍUu@¯‘´¹ìѹ…¶ÔDZ·ëø¬¬d6hM“ôC°Íª\£ 0ôNí¾‰sJ óª–18%£ŠA³$ºfnßBEHeE»äŒÀZ…¸ƒÍVÆ$×  ‹4xiô‰†“4£’ùÑCKe&
+±à¸ %º¸>bän~.,$×8}@碫ý9&­ ¯yX*ì²*—|©©sƇ*\ˆA²N¤ÓÛµgçêy;0^“„ ¯—j¼Õ„J”©¸”ýîcŒ eaÄ@Œ>«í¨ýÆKÿ‡¼x×_²®º~7ÂXí'G³Ã‘n,¥£&ëâ ]€*2zmÍÙ/RZIþ/ïÕŽdGîâ¸Dý;«ÌýÓE€ý<žuôvÃFIl‰"A@¼|ÀŸ‚[Ýz¥z_iû°Y|Õ±1f}™¦÷‘
+
+OõDÝGw…ßzáÂ!Ž¾uDÐNU
+°Ø’;`pÊù2¶¾´–"ðªÌxí²DæÛE-3{åñ9Þ’ ›—d€ùŠÁnÛ]€(¯l³gĤ¾Ù,,wD2™Öãô¥K¹*Є™[¥müW)¿o¥hDY7 ÉW@¦Å‡¿é°g¨0È >8ǽÛ3"ÏRËãК±'cÊñþ©2Þ)aþ¸Ü'aMEÔˆ—ð¡ì|±Ýœ·óœS
+ÏTž¥¤2`§þëËüân›ÞmUÓ%y6 &›ÀÆĸäšm†Zw°g1p€þ§ïî=}dÍ0[wÜÎåTÕpÓRcñ-Â5üMxm<±¨u¶!åö¨uùPg›Òˆ0Ùü~°·ÞºuÓ쵺òÖv4’­G+ƒM¸ä’Ö_9~7µ}ÅS‰÷&Ú•þãÛ¼óMk
+lœ®æ›V4ÕäA°¶ ¬aG|aË7iÆå’DKbÞE@Õ¿*œÅoð^¦z¸ÑYá“VcˆMr‡Û‡L¾(FÖ!ü5ÆkëÔ¹z:¸êð@n†¡,ˆ<Î
+^c+×47ÛãO‘WµŸ½¶h²YrûYú?ÔòÔ~“å’€Ün‰Äˆ¥¶Öí¿B„AM²pQƒ̪˜¹½Â^n\T“,„k–­>=–Ç —¯ A†í[ChÛÐ~ƒÑï%©<ÓïN'“úh"•*ìaŠS+Á]䬮úQX¸G©º2U+u¥ :K8Ó¬!àµ263xç×áGXg ΗƒÍ`}`ÖYVuÙùs¤»ãëƒpï"Ë«{†²^š&»Í8îB W±Q-ñ3hèe1=ÛýšU0èï|m
+¥Õ¬ôc¼¼‚´–eèÏ`m½+*µ‹¶wy¿x•
+e³]×Ëjìaá¦x=ÿ°£z']á±Iý$‡Õ)¯1»Ì)`Å9OÛ$ÁZ–še “‰órU·F!ËCmäñƒÜŒ\§Q€½ŸõR…>¨”ÉÊ m,Îõ‰©¸ÓDÏr6
+“±2;À¦Èɶ–>Ïr3à ¿…p—¾r?t-3»Wçþfóˆñ•“ö°XÀÿ^%ë\,ñ sr´ãßÿ˜ïT 0MÖÀòóïL Bö^Ž·ñä}Ô¤ìï¾ñȧHE]ªí“ðiz9‰S€-Yy³0·zg±ú£ÒÈH»u®}â_;F­(|â—ýø?âò™Gü
+,g•GÝô+
+íE„ íÇɧ¨e—!°5é½æܤÔ00DOf9*\Ús¦™Š¼õë{r|§us¥’ä±@AŸ„§æh-¨¶¢­Ö&)ÓNÂ_b¼÷€‡5x^ÃĈbúgöÛõ{wcQœQ^Š"yèåQÊ*lãò¼ô—¼sðA æøÝÎkŸjüV¥XK&ú‘ˆ.Ç÷ œ†àì!F°œ“àFqÇ—ß½ï.°¤Pµ‹Ù tøyá
+êé|ªQæ& Ÿ²+“Zš„€%¯ TZ²èàÌ阾¶&±¬~Ñ @b¿un‡¨Í!~ÓG°J¦âMAÌ`ñšÏ›_¼À#׸ÖÔÁÞtð,an"µÿTÑm…_úñ3Ç©þÈà;Ÿ§JñÛÚ‹Ï3ÁÚÅsHÍìh ÇVMÚ]ٙ诇]ûÒþU4— ¬©hî¸T€vªì]È„uè
+œ&P‘°²ܱÅSòLT|
+O” P»ñ©’"Ôú%J`cmú7tÖ[lø‚{ÐÙÆE]æÂEÞ˹;K…;!ÛÍWrŽݯ,tû(Á ‹
+pÃ(h•¶
+!µVµ—íphé§×JLÐÐ
+“–.c@ àuí¾ìƒ}r›š4á÷i´æAYžÍŠnQ"ôsîõï÷€ûf\J Œ,P¯T¼ªãkDS5žê%µÒ ÞîíÍŸ`É—"ãë V¥ùα>Á áZø|Œ\åfçaPðå+lÞ² pím|¾}rÿ½[¥JÞƎàÒw¸",6NÚ‰¶{\‰=ÿÊ@|ÆxÀ'è|›Xu$ˆ÷¯ªd*™ž 'Û5½ÚtzãSië‰õ2u׎Ç÷NQƒvd2ÖÌù4ÃM~©YL›cÝLÔõ`ýÁñ^F±fêÁlžj÷c“lŽÔ=ß·N±T«¾œ±Õ
+Ñ#Ç"ÌÉí6ª;P$%Ú Ö
+šÇéE€à›Õ>ö(ovWGQrö`ù¹dµÍ>õwšÈ{¸†îÎïµ8f¨õ LFŒÙdÕïnííR¥œâ÷)áí§ÊÎç¤ê!7‘¥ p=Þ¥¾»iÀ A`™-rg•œG:/_ÜÁ¾©ïv”Ä8²<ý$“tYü5O}3š ëóý$ø
+ÔÒKù8ÃJ™``"ÊK ‡ªP÷.œµebuF o¦ˆ;ÖùŽ#¬ @ô×ot‡ç]mr¬vê’n㬖Ê.aÃ9‹„îØö|ºoÇ €ƒ
+(£skèu®uâïØÃMmB•åç†÷Α÷‚ëg± Q¦I:¿D­í£HçrU(è
+ puMÖ¢áô‘åí¶´]xðB½+vÕHà§óL£Ú¨Û~œòÄÊrÑzMXÛ£íÞs
+1z¶ƒ×¨‡µ¼‚{¾ çÅØ}fÝ+uᤠò¥–Ù7‹"˜t̓E€§^kfºõÚ\=·b·GÉ6šÆ¦óÑ2ÈÝîM²eN’¼xóDËõÎõ1?É
+Ô•”?.
+ØT0-`+©,Ÿ ‚…†î­Á #}Þéþè¼H~¡©Åkç…åŠçÞ7Ê؆¾ë“Š#a]º±<´2{ÉMxmLÅÉ5/ã :¬Œƒ¸u‚ !<•!ÊÔB«£W&E¦WÊœ@í‡c¨´þ@"Áï6è¢çվ̵Ò7)Ì`´“‚µS„]kº6+[)á±}̤ؗ×kÿìÀ“d ™Æí*7{
+:¯¦fe]£’¿¢%Tm–¡ñüW h)s‡¦èÔY%S$„C•OåÌ~ 1ßcRÃ6Bdè’æ+8µ†äôÐ9Þ!ì½ZÙ^$å¨otY.àj ŽU«@Ò¯Eϵ›&K¢‰'Rÿ+›ª'—ìb£Ôg3M~Hm3”Fƒ±›~ЮgÉŒoÊjïœ~®§ªU(!J¾ºeÁ‘ÒÔN8-„(ä3äs©û k¦
+ö`(Pl¢w‡’úñÚPdiÔŠe¾ÿYV†0]g•hŒ²´Ö _òÅ)8'8Ú+ƒšüîõS·ª‚ã4XĪ•`d‘ñD„@hŽ’¤éjl/œ¿í ›m
+ÝQÿ—cØagE‡¨ ü\#ºõyâóÓÜÍñÝ~˜ÖŽÿUü@þýpI@8Á_|¤#bÒ)Ôƒ˜4ãKÅ¢
+ê€ÅêÕÀÖÔ9V*a…´oȲî7jIßÀñÞÖ¬Çmu)sGßÙé*Ëde`éxñõ) >ò|!^‘=‚ÚZavC’ý‘ŽK!êòèA‡\ÏKP¯ ï!IyÄKø
+®WÑcÇ~„S–S+Þ*K}srÕRÓx#- |ótk¶G^£³bƒ‚Ò@"ec@Î{öÐ[´¼E e˜ë@¨0vÀOpŒŸ«‘«Q„õÐt­o8À\¤b_jx+à
+8»T(H¯³‚·û„ôKG`šÖJ4cPXÀê†!Av }g‡qOƒ°–äkU '[‹w9y,ŠÐÙǪ%À§O‘Qø]Ë é¤\v`ôPB}Á©²úÜSe—|ÏÊRQv<ׂieúq~B pý\Ûù\""KÆ’˜lõZ’ˆSPé9skµ¥IŽµŠÇÚF·Ö¶äšU¡d•§P¸
+Çk•¥4'naǵ1^>‚½I0äÁOÇ|Èà€wÃa}dp u¨Ñx<v}—ÄÞ —4±s©a#z6¨hÎØtqó0t!TÅ# g¶¯õVh$Ì05’ 4F®ÎИLØræ†_[.nY{!ø…ö3[lmQxƒØ-º·<Øåð.ß»Â\kx-íÄüN8¶
+³S£½ž÷·¸®b _àž“ÿ5îh´%Ü–œ;"o)Q‡“f s åŸ™Z.{ýTãµìØ!6*ã={„[bvŽÙP.0&NN«`Eƒ'X¬k­ S&åå'\´p'S]–^ÊmŒÇ†+ãË *N'd,šµÂÏMAÑo›»Þ=ëóDóÝϧêÓš}×7ûRÀE Á<´Ë°Ö™ÀÌ€ ËØóM
+:©£´Àä «­>`^¿àG4¯¾pIžpf°t3ž&ÈVkJÛ¸ðõƥ­VÅnŸÂ÷YŽiÿì³E•æHŸ•Šš p–AÄæþ!†)ðþ"Ÿ‚‚ÆâË5Óè5ÅF ’˸
+zrÛFÝ_…ŠG]ÝÕŠÍl‰`Ř|h¥
+:—­UrµcÁ.·É^õÜ7NÕ¡„¡¶x®,=7‡¾©¬Ï=HOé>ó·4…×jý\£&¸w›A& ²™Š_´­ÔZœõʪ³å¯ÒäÔ¦…'ÂçW
+®9)7á´œÁa€À¹ á:î@ÎÑÖZ£uªæÔ"&½š-5DR »äð3z§*É9ÝSt¶ò´W²­èmåñ¢¤§tÇWJ%,B’CP°‰]Eë„CÔ‚£Šâ…Zx_šä¶káY9ºø¸ƒÖ&ƒwÉFsöÞGþVÎY­Rô9q°4½
+ÈœiHùižE¸}¢ç)‹FãÜ\¼h´Y …ì¦%¨~ôl§µÐÂÔ¤ŸTâü¾õÜñ8 `¸Ä³‘K*àìÄ%œÀrÕxhÀÂþéK'°Üœx&°\4\„°˜Ý#°\gKÇÀj3ª&¥z½”Ÿ=«Å•®T;!wm³½µCnQܤ}à°Ÿ¦æ»w,Ú?…ð—x„…>à }­*Žœ«áƒâ X$[Sq4xTœui³j-þó\í{Źæ€=Ý._»ÄÞÔ`[¯]a/¸
+á¶m„ìNó¶â¸×Ñ­æîÕù¢äWŸÀÚãx‡ø;¶}òÔDÞ03ÿ¦4|âzªVD Ü
+–EŸi†ÙÏ&Å“CôV1¿H«³O ÅUµÈÅJæw…®Ã‚¹ž;¿~êÿ”WÛŠe·ý‚óç%0óÒè~y7yHè\˜B{:è!8†ü}–TUÚ{K¥ÎcÓ®Vk«ªÖZµj†ä;&®Ü†”³¨^Û]©³.&ŽeOƒ§µÛˆ»Ö{Ï6×Eϱ‚
+ÝU°¼1÷ÓÔÁU¬Æ†¢KAp…~2°|‰o KÁ ŠUí©Û´–ìß(SwØí¤¸fÿŒóÌ>û»–üvŠâ$HëjýÊ
+„ñ?èzgôî ÁÒFã€j<{T3òO$øWÁƒ+™ŒØÄ JË1ȉ‘pæ˜f‰r°" ‡“i<óšÀ ,½ê  gÕ±i¾ ÁnO&‹„ ŸmNgå‚U883ˆó}º]I˜AX~ƒ÷–3µ§Ží‡ö’ÃA‚9_½2zÕŠ«½YÚxm.NúÃýØqëéSõpÇ³Ê s§
+¹ÏK²ò±Ka<ò:5ê€Émîé‚£‹œ›{†­ŠI
+ê^nª ¿^ ÔIüÕ1±)êøQÕ2Ó&žžÑ¬]•„|¨R£Š’F‡uT’­„œ[yžáê°TǪ6‚_nßßêýÝûûÇðÓ¯>ü¦üåןúîç/ÿþûÿúË/Ÿ~þLÁï>ýíŸ/áw¿ÿôõÎÿs·ñ=ÿÿ~üzûr³÷ÿÂÏ°UýŸÿÁÿü?üóŽvÿz÷ßÝü³¹ÿÔþàO,ïñλDäÑHn#O#âCþ;-vüåg<ÉГþ  ‡·µ6úûב¹ÔÐ[ÝvQS²“ë×[\b¯d)ÏibW˜Ü @o¯à5RKä‘\ŠaÿŠ¿ÇØäáÝ~߆ ™ÿT ®‚à
+­aAÞµ\G-D'ÅX¦¡ šûá}Žù ‹ILK$ê—sK•Ù
+båa­ÇÙXr¥0~Ô‚y¦ðçªs" GŽþ–xJpªìél{>ë”O¨…oÅÒ8WV›*{þA‘s–‚í·Ý•zRÜ•¥*ê?ÞÔ’?ݶíQ©ä°Ë÷%áùªø?Di§«öÔÝ÷a!¢µ§0]k`+%è%hs÷=ÈÛže£éElþ#s1bµ…ÂÆ' õì:á#‡k yÙ`=ÇC% (&É•ºç–|÷²¸Ddqå“J¼…¤7­`B]¥¶j” vÙªuYk8—–)=—ö ¿*š¨è>+ã×–Á]
+ì~ÁÇÔ“…-0Ù ÈGÃbkæ¼\î ­q.LyUJvÎ P 2àP1–¡h›”3…\$-o}Ü
+úW=]Ilù˜ò¦õùkV’ÕÊ%uËcwÆðV×<~Î)/àÛ»LM¨x?H ÙsØÑ|GЧì$È3Û Š$øÌ9pj§³ør²ý øutv û0צž½“¬L¥{cöŽßÀ«‚F|é’ðãN$оê“Ò(;žÕd½¤)3£ÏW^z„£P›rßg~î£NÑþ¡ö·}í­¼ õhÿs ?]_…û‚%á̯â04Õ„øD/tÆüåÞBHOÉ–ËǶÌ[ øzÓî¥j¡öÆWz—7þØHÇcK˜»ÀÁ© »'Þt#mº ÁŇûÌ;àoH¢ÒiÊwÓG˜™B*cÚOIVZd:MÄõ¤ß ï¡ÕÅdˆ@îËØsCŸV8yL› u©ÅÓ½p(ý©9c7°‰‚
+¯·]Å´Ò.M Ö £®›Ó1:c]äš8¼§ã¯[n÷ñ$t “~nŒ}ðxêÔV³¹1%¯ÆOú%Ê·öVG‚˜Í¼ËÃò ¹½«—5Cø õ¤É$©ŒıͦŒb•ÊÁ*›Ej›IËz¿«¸ÄƤb»jÄŒÇ?—6ÝŸeµØ0</ЈòÃœFëéìÜ¢Ó"ôàãu÷Æ(˜a]ªç¨÷[ÏI…I™¨¹ë$ÂCgEìÆÁ#—FìÍúR1:`h”Ü8òCèC`€G´Ô¾Ù3ø²Iíl4‰ßeKLÖVÊ£9üØNÁ.¾BÄsh@0ÚÔomå‚þs]<oRiBöùhNl%pCtÔ4,¦ÙQ½Òà3…Gþå]µ5¯¿«—Å'mR š"íÔKÕ¹&jú©*íªÊ«X_}Ç‚gù;–hŒR¹§ñôåÿž»Z'6]Ó;¬aaÅ ‰û2Ž…“ÊèV‡¼nTóðÍpê5[ø*@]VÕbM.¶Ò¢‰Ð*X¢cb
+Wëlß(ƒ¦"ªÞhÚ¤Ú½€ÙHͱ_Bïv°ÚÖgš¾Å{ö.æB±¸~1[áeqH ·]Ým|°A<QBV¹;yÛá„¥~@qEVMî Nö!-ž tc‹k{Î4Ï=Û‡Ø3y5µ5ßJ®ž×šþg ¼¹‰Áqa°òrÐûX¥Z†û‚7$j·mí–°4ÿÕc¥x2¡Kµß°jàiŠ<V:Ã!JÀEnïxµm8zSÄ@•"óxm™¸µ¹¹´¶É*Uy£€‘®õ-±)dË>Ë÷§¢l=Ž²TÛDòKõ³ÆæDЪ.ˆÕu®kðH¾¡"†ôÉÆÓ€°ó!p8ºÊgƒ—£¨¡XŠÑZZNÄ dúÚh›{qË­[”->
+-3—lѱ±"Ê x$vê®2yt÷tÁÑF ÎÝåÒNëÔa*”ؽÈòyû×Ûn0¨#d77•€O·=WUR« h…**œ·Ð×H²jî·™gÇ T'¦2\_nßßêýÝûûÇn_6;n½Ê-ƒµ¦7ˆ"Kø‰ÃiaGaš=™‚>¢eY¶ >é ¹"ñ`¶v~9°6DÙùB |2j_+]ljAÁ-¦·Ì¹ìº]Ãôʾ2^.);ºaxÃv¶·Ô_.:zú>š/‡säÎiØH|…ßjªfÖ‚Iìïzæ2ºÐýžƒq³É±9Mt[‡e?¿Ô{cáÀ Ìj&V´–7‡„›\“צof¯ƒ(Ú(\¼%VYd],å6D†l¸(G_ d•Ãy˜XÈ&›
+ øDïG à¼<Ÿk¸$bÓËÚ/ˆÌuº
+H‰¬WÙŠÉý‚ûõb˜yp‘‘kä£æâ›ë…Á‚ÁÓ’¼u 3Ièï}bɺKe5ƘAšVtVd,'Nœ¤Z——Ó/[¯aåÌ´pŒyM1¤ÅŒ­æhÆPû0â§,Æ´¶Ër…×Àj­kbâå¢Ö¶ÖÄâ!ÑŠvñPÊZU3­Ô¸©1¯±Éý¹¬½v¯}m¹ÖóÅ£ ¹Ém©¯µªƒל[’ÚZÙÁîÆlƧӇSXÎ&üÍø“–ªOZs ñæè‹›S…‰)”ÅŒ‘²×€‘)6³¥Ôºe°/×È`ZÚiv)¼’î®.ûŽ¸îë=Â* Í{(l÷ÂÜÜ´«Ö(l) I}£•ÍNˆIö•*>Ѳ¢Ý9“Fš×wjZu‰)"æ2l1†¶Æ#\.§CdM1ø×ó<Ø‹g•:þjø³ç…nR‘ú'ü±jZ¹¯pæiÕܼÛ0[‰Ñ̪}ͼ 4­%Äî–”Ë>UZ¹Ñ>WZ+7ó’a¥Ä•‹MH+¥eèùhŽZõèÀ®Ÿ]5 iýù4mëåt„€)Tv±žçøˈNÇ¥=²÷©¬Üƒ.tªaLU}ælgŘúïâG¯(†1Áì°D[KjÞVT«ÚLQìÖëŽßëTJR¸ÌÝ>F;ð—{ÅßÂT=±‡—Óámó°2‰=€p$öˆÖ¦wð?ÏC=+”»´-|æî, s¦HÖ/ü.êmLꈳº‘¸—]ZyºHðŠÜ­å”ËÀAM
+x\–:™ßºR0jE…
+’<Ïã½×ÞÀâä6¸í­=Ä•ÇÖ¹O­<f<R+ÚŠ[0Oaÿˆ¹ó<ÚË|ÀðëÜùævË
+mh6ÉwA%Ô¥°çE<úXÛ˜ú”»‘umf,Ð4zwÙ+óñè`Ä5¹+®±´Ç¨dñÇ´«¶à+ïØX>¬F7Xœ¢v‡˜ó4܃r·>
++›‹Š£¨µmÄY ËΆH‚
+È«F7V›ûP§Ê¦'Äܵ¡¬)Õ϶RºcÈäFAObÈ«Ïx+Îý!s8YY`Þ$,j&ÝÂÂѺ
+ôTÛ€bË\eNÞp3Ø™
+2ÓÃH.7!{œ8Vìµïj‹ëæ»ÚúÙÛÚ
+ÕÝÜ—ür:lÏë¼v°u¶—Pš*X~q3†-Ä»5 ¥Ój¼‡î¦Z»JØc<|$QZ˜“í=j¯œî†CÕzÍV@à…“Ž Ñ™4'³š²¹ÔìÁ Æ”‡†¡Dî@Ζj$«Ikñº‘tǺuãh˺q¢ïÁ=¬„Ú®§lƒ/¯‹¯æ µÁŒcÏÇmFØBîË´‡©›‚M²/^q.ÍC –ѯJºë"áV+ØaÇ
+ë€Jo{Í)†BÂ(‹º0£¿š+ÛÔz²Ê’H«:8Ž
+ðP¬ó@á÷]N¯Ðä”PÈwBÔ tSÎOIÁñîï./ì
+o(’%®‘RÂ3/Uƒ}í#„­¦8¨<¶%²ÊTÜæcƒû3ð¢ùW`³7«MøEpÕŒ¸‡,üûµŠÓd­Š0¬ÍÐEÕÀ)-T= ÅRbâJvàñÑçÖ€ûÑØL.Ô-SÓ°)€6µ ï=ô:¥Úý$®&A8¥eZêó±^‚Ϩûg›ðË˼[—Óñm³¨â†Jaô2JŵEsÙJåeÕùH• ƒAg“ÐÿŠ_küµ Ú$_”7£û.“®‰
+i•Š?rj†+¨8ˆøG`Ô:–Ú0€7ßË·ØÚŒH— æ·¨ßà•Çl#ÜNì'ñŒC\Öe–ìùhý#Ðœuà1l:¹öýúB¸ÿâf£B”¥§hwaȃnz”E­ º/Öð:-,'•F‚ §±}´gcØÝx¿œŽˆ`Æ{v9O1s9|Þ˜Y §Ýžâb‡¡Ô ΙÐÖ„ß&Løáôé/ß|»¼ý#~úÅ›_óŸõñÝw?}ú÷ßþð—Ÿ~ÿÓG3~÷þ¯ÿxgþæwï¿,þ%Æo²7øóöËéþ^×ÿÞ~Å?~ƒþÓ—%/¿]~üSXÞÉÉï%xlÖ¥ÒÔ…ا¯ª«å2,xœ ïîÜ>=0ß|ÿQƒúýÉ‚"
+&rb[Ô5„L “ ¥\Å©@úáëÐÊ*P¶»žçžŸ ¬+U}`è±]Y S‹î]˜S2Ò‘oê¸BŠ¢…lê–Zô+÷žýÊk•0 J©/"#ˆ¼Çñ,*Ôª-upãWWqÜÙõ|M@øóÜï5G& ;Áñ<È°„æÖÒ©œ“ñCÝ2,QÏ‚kZ.´exçwä×±1•¯
+ôBŒBdV™‡IHÅÄ”c¨†T¶»›P†Ÿ†…û<wúüÿ•þ×QáU=IVB–Ë°y’nã _í-7_Él|:Ñòæ_“©&.9T²%ià)P’Ч0C+{^@Ÿ&ù"°^»‚'‰EmcO@%t—¯ð(51c{ko
+ìâU”»ìý|<,ðú¡ž²]•JѹÅÉ$££A¥^\gQ¦Ø,l‹ ã61ÆM|ì+àÊ>‹˜@Éé&ËÂp—MΨïÍ-c8ƒ(ÀP$· ×á¤Á\B«h?¹_}éÀ\6.Ìö(—9—W€±‰›¬.I8Ë~7Ànq+ØËîB?ê]Ñ%ŒÈ§ZTº†í{¨ülùŽ%%C ­ÁÆ"Û¦ù{aŠ<c„*C•Ý¹ $¶oÔˆÂð'Œ©kpRK×’£ÕXðdÍI %Ô¸ÊÖ¨$Ž5±‚"sÐlQ"¶•ŒåÒ=YŠãûl:QÍúc1Y#W…h™ çž!·íû`ýÕÂɯÏlIÑÁ\îò…A)¡b_‰ô¤¨°'Œ¹˜eîÆ^E†&$kCËÉû3¤ÉÚM.á®nq…!TÐd"lŒþ¹*Qâ%gŸ§¦iÁÖ²" 7å˜Ù$¶¼Xv=öQ ÕâW@‹‘ 'š+úÓ–Yþ^TŠHš ª˜½0‡X$£˜“Äë‘s Ìf£œŒÅðÙÍ B÷"²
+d©‡fÍö}±WÙ“kÖÀ6
+媫Š¯H¹+û$ÊI£3Dkdé1[ì5'#dÁÊ»ÎGq_‚'•íàdá yá_‰G¤“0UèAÇ—|v„=ªu£¢ðˆé³Ï4ëšÓ
+ A¬KnÚvš?Ç‚ŽŒTP“ ±‚pÝÃ#N;Ö
+9#Cù‡[AЮyD‘Ûz5—°CÀÇbL4äš Ø¾‡.À
+)Åì¡`ÎÞS€Ñ“ñúRƒN©:iÊ=æeòÆy±Åèô’òµ¦)*Ø׎3'ç’gÜ#›ŸñSœÜ-„HÒpm¾~PÛˆû°NçS:6J4­U_¢ü`A uß‚Bó¸cFõÒ"[-…ƒXpg<
+åz®û8¡ti+|¿Q6¸³[Òäml
+š,BS¸Êòü¢ü±|Ù9ÓdD†9v­³r}…Õ5,• 6¯úU1SC=Ô³ä,xH×|éq¿µ`n€FMåŸruàR¸z¤fƒÝ6mÇóÑ²b›pWמwýÏÀàËÅC¾(£´› Ï»ç`‡F¼æÅM41VaÏD`NìdǺ*tG y"¢@e°ãµHù
+ÏX‘S­€%ѪŽb–ðèæšÙçˆoAyÉà`-+ýJ5 Ñ°p
+þå·ÏÏßþôñfö+Ö)þ}÷3äÂOQ´åf·âÿ}÷?üòüðo@?ßúí·¿þ­Ü>òÂ_~ÝÍ'dBÝcÖ ðÿŸÑFwøé5ŒÐ Ç^Xx»_ÖûþôO³Ûx”,EÂêXtqi:õ
+t„k>Å÷ pìxí=74ºPôyÖÊ™yۃőIÞ}½Ü"ïôâß÷`(ÔºWog€hÌÙ–@¬(}ut°ƒ”´s©£E3e-d»ƒ³˜?—3¼F\ðç¹\¥:<¶—¥úyvyqñ( ;0Ô¶lî5®ëgnY5Â1øÆÔ[—í6Y&áÕéY‡LGœE¡–’gQÇ2àÏ{7„[ôæÿ
+ Ïb—ëž
+'Û’U¯HYµÝNdò²ºG¢—ÿ½Æ—ЈcÏ4jñTtÇl÷û¥·@ëÉî”›n3–Å`‡UuJo©çДÍdÕ+P`Ý¡›“fôÑPódÏÛ$`Êí
+Òj€u˜0U*¤Ø‰ê1jòúmܨ “êRTŽ¦9¿ÇÑi-‰e(¯ÃÅ~¤-¾F¾uÕ¸+¥c±¸8¶÷P©ù
+õ¨ûm©Xì”®£à–U=ZÞKUíM›÷A;:[¡ÁtÂe8LãànêŸZ†]4·Ø­äH îè`*þ™SÒTlë±å\=€-p®üÖ>ñ
+¥ ŽëºoôBîW–À
+Ðc©/]§Œ¾®§z–X)‡3nIä¯Z—@ë¡V0Av1ݧN1«Á@£êLàÐw¢—aI PyX¦U ¬šJPOpu%k
+[%µ!…ýc^ß­¦ ,Ñ­˜mõ¢¹E Wesµ2t"g'œ£³s -„³Ÿ¬iÕL"ßíF¿R{Îü–…¢«sìÛ{îgX…ŒXx ì? mD³£‡_ m:ª"vТ² Õs:F³91læÑ5cŒë<ºpÌ,Wdãxø€¯>=äé ‚ñH»¦ÇŽ!ÎùÚ¯{TÛI¯LnÖø!¥wU1+ý:=ë
+1‹&LŽº×uN°Ý³hS!smƒ$U_û­ÍþE \ÁÔªA\¥í»åÉý(9µB±N’³:Õ
+¥;ic­²o/½`ú•8õ Ó0_Ÿð&}ÝÛFí=¥,vç€47ŽaèÞ¬.öM ËÄ•òëÎv´&×zõ*qÐ@kEàFyjÈ
+×âœî%œÖáÀLl|*‚MJÏln:õWôÿøâxÐtc`J«M¢©p.EŠ3³x^¥pUŠç™ Ï}s{XÀ<ò‰ÌÕ¾)‡­ÝS½$ý¶—@}‹ŠáôXfÑäŒ5°v†(°S§Ï…T"Ú#‰ß<
+#%HmmY„€-ç/@o4sPŽ€8ÖIþu@!ìNX^à¶Î‘PÚ8k5Õ³(=Žµ¡ŠN³y‚›·5»5@/_> ç¢X¶„K/WTæʱ0Ž]i‹%K,DóšZ‰=ÿ˜ÚeWpTë\Ûn`C¨ü+pY*H¬×f‘‡âc•´yåÎÛÛêÑý¦ ô3ë¾ ÜÓ
+ÎýW…
+pQazÐÜ)¡ª#íÚ–m„CŒ=x­Ö<ÑÝ›ØW–¬_j!øY› íw³‚BÝ ;Æé=u§ýlTÎg-;Æj¶†>u¾ÿÍÇ.¥ñXü—Uj¹11è8¸
+úrx]HGç#&ZÌô[°Õ";`Ç4y¼¯·Vhˆ»lUi*À}lù¥Ù½íŽ°@ Š.ÌÅ“¾Šî ü´–”v Ù|u©sïÇN•Ùª#­­ïÑR¾±2ªS;•]b÷_ÙëvŒƒ«É´öÖ°•¤jÀæ.šv1GY€25Ãg¶O}wptï>´TúÅï‡g °Õªï¯ºL`ûÚtwœ†¢t[6²de´´¨^÷j,j{õ.²‘z g¥Ý] F»^ãFuW‰¬º[M€£¶
+W?½ÜŸü´Î[àÙ Äì´‡­CZj«Ü‹åÚµ¥¥pË3À~$Úàc póÆ®nZ¿Õ{7Ê¿ŒmÇÖ>Hqµ|<À¾}f¢tÉè¨÷u-)}Ü
+‹P˜ŽVïw`Á ˆ‡G°¡ \G&ù$ä¥qsÞÓ(´KÓ(‚g±+«#??ÞƒøšÕä+«W uÛ)äF¼¶ ¼~Íì‹€V'À]ƒ°çØF¨Ho­?û'¹Úç/ÅEE‹Þwu"8wŸoàÚG„WW}ƒŸQ%õZfÜkJ^P6³µŒÙOÀe
+.ñ„uÄÚUŸwMʽ.Æà‘¤ðÊ[”K©Sü¾dª
+ÕEqL'AçÒÑÒhžÛ@%þéó1…Žg§n%–öüüWö<Vùðÿ»š+lò
+5*RÙN™mÿ~b´<}¼ó>eê;à åKe>òMÑÊ<›•Ìúõwè÷ë¡ÅqfX¨¡¦Žšqdòöž#ù¢–-•s¬‡F~ÆÐ ƒ¬ÊÖöã µ!Ñ*Ã…F Œc‹¼é˜˜Ÿ>j?ô„¥³õX<0e´Þ6ÿ|’ê.ë׊EV†M\%Ï÷úkñËÑΡu;+ÏVg‘T`óì0ú#µõœ
+•öñ
+aý^Á9#îýyÁ« Ûi³½úŽÏŸ¯­ ƒéTªw”Œ¯Í¡¤5aJšJ
+/õ£(ã¹Âê…Ú“*KÒÀæÈ<ç¹S
+ÃçÙIXÙ>ñÈÆCA¸ùý)NÔÅk÷:². 
+ZM[Õ¥šy¡òôcM®o¼Ò-ú§ïWîM`€·Ð
+2‹†i}Õð%t~ÈèÂXŸ/_‡KèÎg„뎅µYؽ°?wÿÌw ï KGŸŸï8«ä·Ñs|ý½ú©5­çõÌ—r%Éú¥÷ù”ÚÆjÝÌKÂËå’î
+êyXh!IW^‹h‹7i¼0Àý× ™ -eÿžì®Ðó}yÿË–IŸ§õ•]¨mOR­Í wE ŽúV›}M+Ç+›]PæY'}ž|Y ^'v×l €eùÎ]‹µJ6¿õaG O]»­iƒ‡šäbyY›}Ýs${+ðÐFÜ® !™i€¯ž¹HÆ“üº4±›nY:+»åûô^‚ÙçVZKîa÷y0qëm“`M}ÜkÛ<×y@Å,]”4/ JÛå1Œê<a86}¸k˜M}Ëû_¶i´êx=>I'?YC…|¥m®c¡ÖD\ìR÷
+» Ï]7J€%úTÊZ îݦ5
+¬m´ðzçeȇu˙࣠. ïnãþÕ×÷ïÿŠŸÂSëäÏ÷ÿ¹É?nuûý~ò7ßü¾ÿýwŸ?|ûÓ/ÿþÇŸüùç?}VðÛoÿü|€¿úîóç?Ü#} ›ßàï÷¿Þ~Á?ñù®?à‡úõžï¼ÿð·pÿÀ'ÿrûmîh šuHCoƒ°£rëÛá×;š'¯¯þýŒ<}úYüùþ¾ño º$¡ÑŸ.á×ÿþ$¿€t .¿ÀðÊ9™uŒBª†¢Ð‘ÝK¡ÎcËÌÆö¾·Û
+åùlXWžm•(ûå/ÿGhS:kÆ2ðö¥ùhD(µ^v{(}Rx
+gbKùÌEú¶$ÎéL¹—‹l~óÒ!‰ñÕ-ÔbhK–ZbÕˉL àL±@N0\;œ“=æ“=vNÝä«Á˜ò‹qbu?»ûyN†£‹¼-3|Q ËÊYÖØ™U¥½4Äõ¶bRXÇ
+Ùä=% í.rì„b:+
+2Fuw$±… 㽈4BR²ÕëP«€Xèʨ§° |o¼¥&Eô|¶CL´ÍjU«ý­š[¾*Ü›;ÛªÕÉ#aÕ4€~?%Ëò\(‹‚ZÞ¢@—…|J”&yLOãñÀ™7÷¿«L\d 0Ú…TvÐ†Ì $¡€cIÉÀ […LM¼JxÚ‘Ò³£¦ai§õ.QåÇ ô¬Ç‚ öx¯,F·IÃ*¡¦Ø§û‡›_É\†fQE{‚U³¬°e-.ê–³„~§,Åm£¹ cÅÛ5Ç)qê û«¬RE!Jâ
+EùdµnQ±£mý£ œíHM짰.²¼*ˆeå,ªìÓmJÇá*pAÅ’´+~5g`„¢KëÖ
+˜Ng¡ýB£“Õ v—™Xæl‘ßO›Ç©³lǺØÚØØI!ö³š‡)L|z:í²²öEàJ›
+¯§=æÁ{©‚1•SÔzصÓQ2¡F£¿ïìXNT·ú;±áªtÉÜÄ𜊓žÂ~¸Î)(BÝRÚDgÔhF¼ð²am¤q¼×A'#ñ«>&dŦ‹y¶z?†ëËmÅ‘“± sÅûEŠ–é\&~bkÒÂqÎgì¡)œkô46¨Á.„ÀrLiW™…‡Jðbï~-–® |uäÙÀ÷ÆSïÜLg™Ó4ŽV1•µèÄ´M¥L©OþNiÙäûœÂU®/ÊbUAËR›ØDñÄèÄ
+- ·U—7“rz}-¦ð
+Iz†Þã¬A¤Q·ž5¿,á|Rå6ô¢†¥íÞú¤’î8•¿ƒÉ°Ås¸<@÷ÕvðŒç˜«”ú—²»ª„eͬêkÅSpÛ º.ˆÍïF×£I‡‰*éÖðÞC‰.”E]n S«Óâp6¾«¼ËŒ^p»JÃ*]‹Ìn‚˜Ê,c‹$ eÚ;÷˜îãÒ¡¨#¿ÑÖ­ÐØ`¸-,ÈË9u]gHƒA^mÍÃ{
+ú‚ØQP#±ð’$íVœÆ;¿+˜ó0W°ëf{©æãy¾Õƒ^x¸å"êA+"Oœ»W}l{Z–
+øm}BßhU
+ Äs²-tŸ©: âÙ¾“iã‚ðx,¼<ɵèv£‰ç‰t»—JÁ˜íwÛR”ðDj3·«uì”u–‹¹†a`‹R™’ߦԹ[Ë,/êáÌÆh› žÉ:ëá)ìÇí’¡%—ïöÈÒš
+KAñœ»)¾s¸ 1œPS:+Á'¥‘ÔBâ6£àÈa¨ %dÓ§ °’£À9!½‚ý]¨^È•Lk°ÙX,u!x’¦Œ:¼Èþ²Næ’:Šá9œÇí2ôG/FÅÕ¹Ìm$2buP6ŠPGv°ö\¬É0´7Y{ŠP=£jAàmÆÄKˆÕaC‹MÉ<yŠ Üj6.dôòee'“‚Xešž"—B3ÖU
+拓縷KŽ–l.y¯HeªŠÆ¬2WÙCŒø)4²ÊÆ”¯&—‘€± ñýM¾? 5BZ7)í4Cµ8
+VšæSD9jX`ÈSíly—rP¥]P%
+–îõÞçíÁÝhÜÈ]–Nø<p¯«¢ Y«'÷ÙÐœŠm¢ ¼(ÈB,”|ñÈ:ãåxe¶!K[ ô.Åé¸
+†š, •z¼Š*zdýÃXæé9Y¾ùí5'{kþÜLû¢põs¢´½Ok¼2rÝNÙ;áù“'£4™ë¼ât3‚Ìioj³„ÍPæ/Ñø‹f` e?–Jyµ:(C† ž0‹r“Õ
+ˆÝÐT7ÀÙ¨ë"R‚íJeɱ^iŠúóú%ý{X«T<Qy¼÷·ë»¯ÛiŒ.'̵}c:;
+ÆPê~
+4‹{K±‚Õ,à˜t| ¢kÊ“Ê‘æcS«øusá%ÍÉïî|ëzŽë༮mŠýdØ=ù¸–ÿͪ‘ÕtÎdÜuSLA= @êM±nE ¬îŠ–áÅ~_r5iÌ‘Pv& ®ÂÀ”Æ!çþɧâ'ðO~hÏ_~|†HZ³@k›.° ’GG
+9³S†-ee„bòtm?£\»rf£5‡×–T LL¸”7ۧͥùy{7y^ž]IøÚ9u4ç+o“N4»13¶?¼½k¥XzÆ´Àï­ê@Ѥ‹.JÙ^>ŠND€9W38Üî^ì2<Ízse›¨ çKºh7ÿÌó-­YFÀ“e©t™äø6Œ¹
+F<ƒ¸Þ¦/ÀLjðzÊѾL¹g#ÍŸ›}ì¾ÛÔq¤â˜ÁUÃÔ°(j‡ñ9*]}oõW:4ÉuÇ~2»dÛ0P¦úeF­« "”ú/V4ûOÁáûïsÕgî#ÛíTCÑXj.Ê昦L$sŠ
+Ì㽟?xÝΨðI;!˜7ŠfÄÖ›}Û™€´¬ª1š+Me–p†ÔšÁÓ÷ãþ
+’ì¥ubì%æ 64?Ü¢ ÞÜÍR¯š
+%éˆßÏè1nc¬»>PM¤0¼h´2z›¥‚›Ñïb8“ÍA`»“_‚»ng4¸|pËUýyC@öiK…ôªJ¹˜)Ûò•k²a8]ê°¿µ‡…g‚ÊOÁ6úˆ\7nÊÖìî>¾ÃBèáéí
+¸d!p6§ÅàHMÁHdj€ËÖv´9Jå l÷*6˜g;1Ä¡hìÓ
+¢  Q/n}nA°¦R4}€Ó°°CÕά4ÑDBçé1HY¬Sâ%µXöVÞ&Ån.>pç‘|ÏÖѯÄ)ŸèH9Ù½-À5(Ÿ•—ÊI]jÒÔ
+×LúkØûn<Uk&;ÊGÂë4±x:Q=`° Ô¢¼ÁH 5 èv&¶‚Z†‚ûõ”*"¡ï¿-m:µ7§¾ß¾à ¼?övè-¨%|5úß5#6¥°á¿F š:ÜŒI€TÌYâ¼iZ6—.EmÄa[L°‡Bnò\¶®:öq?.
+æ‚ZQnZ24‘ÞŠ[‡VMz×@˜E^ˆ4€•ôU¨öÓšš
+mõG"•SàîÉïáÙ¸‘î
+_zäÄtß‘„hêÉÚ (éYœû˜žŽ©ƒpŒq¦ó‰l”•×pŸ{þ¤r/n½¦;°`p’åá½?‡Ä S4m:2kKÅæë5Dhå?b·c°
+Ô[N.³ž-Ã0ƒ»†8êµHð!‘š«0l¹zªAò‚xýÄói
+副#zõ¢TrL šÄ k3û‰•¢LgDù°t—N¥­ÔÂ'õËI¸Ý‰@YÓKS‘¦#M'ã‹NØ;st8ÈÊ$í£-Yû¢®+ʉà ÷1œµ9+*•*5cÚÏŠ>4Öš– aèNA¦øçúCý(‚D³DP[§×ÈRžª,ÊÔ=ÀCðÝù€¢ù‡P"¢m¬õå¤ESNrC8Tÿ‹f±+%qb¥¸ì^rVZ€ªùßÕ3ª
+÷£áLŽ™ô<F˜’D &Qõ«’$-C–¶©ìÜûû™¢Ñ
+uãû¯Ø×PMJ‹#ȯV?Ü3bæã¦ÁF¹wbgÎv
+ÁbÓ½/¬sKX¡öºÉ—ÇöH#`°ó¹«ÓCHÀr‹òæ÷ûlvpÜ»õ¬êåë<z@½0à8
+•b¸hJqöÉ-Qý‰b…XwTÀ ÿ;P×±­CÊ°S¦‡8f´›äØã|ÅüBƒå&ÄŽÁÙ÷βnyË@od•9Ò‚“§ÀÆ͇6Š†‚ŸùèBðøŠÝñ;Ý_jß•¹Ú©!Wµáb¦ŒDIÐ „F¶mo£=ó>Ë#áµ-Þ>;@ùíõsBX!Ð&úËÜØŠán¢‚ÿµ_Úo,sb_Ý}Z]·È$Å#ôð±­âO„}õ~@“¶$.i'ˆ•5ÑÙ“ðÄ9ÊY=óÉÓâA8ßSºïq`q5x “Ü`¢”
+
+30ªaÁúþ,•tËK‘£–Æ9A.Î/P,Uâ#'4wSÈýÞGÇ“>¶xôü¦Tœ¸œç¡P—·Èùe>iU%ŽÄ÷(™Otø˜:¿/¿z²oOŸÝ~ü~J7Œ’[Š??þö$þùùɸ~ÿÉò›]?ýåáç—ï?þ÷ßÿxýáÛ÷_¾ùåíÃðÓW¯ß½ùùVŸáÈøûãÿž|ÄŸtËŸ_ö7üðŸÿ^ö:RÅ0~‚}‡[#ò爛v-¢£CC‡x}ŽíãìüÜ…ö_Çã8Ç6П£ŸoßÓñCm¿¾|”¥«*x–Ö}»ÞÑÚ|ç}UZŠwÙwqøx=õüúòËbûòâ±åã§}Ž{Ó†Ã䧃Èö)JKG
+)gcoËSX9B ¤;@?çíèb—ÃÓBuè‹ßÙù Lw—jÒÐtÆÍuf]Áq*}O[]ŸjšÖ±”õÍ–'âÙ-ÏÃ
+…‡O¡_{ðQ\Vt–Z¬ :ÉÙ%¤áͲïæΖp÷u”ˆ4ÙÁ2dó8=^ãúðÛ¼`ê™o3<Û°UŒ7
+ƒ o„’ñò­ )Ì™o 8¹jjqaõT7Lˆ´–[œfqëª[ià%VmZŠâí+ê6Ý ÎÙf„V8±(†.…m¸ÅT—Wh™.±“«Œm
+lµ­ypÉ =ú zÑ"\t
+Øcf’æk›ÿ³•8º©õ˜3Îò)Hôñ³À¤÷-¼]e¦íËß ÃpÌAQ´£-J¿«°7Ju…ÑGRBs¢¢m4³5B)3M-‡|n
+ýiŽÞêvГµÅ*ø/]hkÉÛêTA8e†–ÍòÁÒtý
+H‰dWË™¬¹
+‹àæp¸õ󰽞mçÏ#\=3»næ·y1u}¦.ýóÿuÖú˜œógOÏ9!×Ö™àúÄÄÏý/áý&…N“IÓËÜahRØ.ïøÔ£u^δ??„íL~LcOz˜§ ³µÆ&¶·m:=1/ÔÌ”–¢¢õ¦¥Ñà_úÞ矪Vp¸—íù„ >àèŒ÷ø¨+º×ªó@OìMSqZŽy.vð«‚{SàÊ8öyUOØF½þ‡°­ˆ‚Ãë]i»wFÊä#!Bpyì²<†Ç¶ß½´Pw[ק†Ðé0Ü°@ƒçÇø^ËÇ P\¤"ПvU¾îщ,ãYù¸Ú$,HÄOÃļʈ¡á´¾ŽCŸ:}>ŸòBÞ.#}]8Äg¿Rnö®qú‘!׫~Ì_ðáõÜê†Ð´Ûå÷Z†òíõXÔ>¦ V< ÉõoI¨ ;ÒÖ7¥¸XÁ¡²™½ÐXª– û$8–|S*j¼ÂZn´"F¯{,Öï–MpŠ{åT§¥ƒ8ŒbÂq+ðÑãtq†o‚è™0\Ìe/6okVý2i¬cèTºð}9ßx >×^md zßuö÷]7~f|ßÅ~Ë<¸öL5*·gh7¶Î®ƒ¥Ã¿¼ý¢ƒ„a«¢ÛAfµxô]mù£64±•­k—<ÈñÐÅ,F%cNÖœžåÄôlž V}’ó-Xd4cÆ«EEòf‘ég‡úhlfšËϤÛ@‚׺©o·2Ø¢{hU’W¡\Цo‚î+ú¼´[e‹×ÚiSqáùM*örE§ÊQ<ã2ô}-Av@ßÕϬ  |úN±ú8J"‚}?÷Zî³*(ºOÐ¥TÀI}<æ$9NÞXo/Kûã8Ég^®ó¨²]dú`ÅÃÖj:XRÕiÐY®’å¨Ï)¬”‰(¬š3WOBpWhØ|§s"E…•Sc€ÖeǨQ“°^ÐÿW«âËú”ß™ wì²4F¿r’n
+“ªFxf8¹¿N¿–¡Ÿ‡¦gwŠGvA’®´Ž¾ó1NC9дÙm:ßùa›Õ¸ßÄ,/÷—In@#ÉòͼSˆuŸçg r ÌÏÓ ‰Ö\EîÑ×rƒr¼t09Î/u|2V?5û¹À±ùmóyk$âWFntê|ü*DZj^â¼pˆŽ'¶Ò©\ gIî­
+9·Þ½À£ɬz êa"‡°6SC©ðcÑ30‰±™}¯ìŸ
+Œ9¤¥KÆR]´ƒÎØxz
+` J¨ÎíuÖ2p§Þ*xOô¼CJmÁN-f©`ÙJ
+¿áòU¹«Ü¬ÍÐþ5µr9+ðÛ8G·t˜Úäé`Ÿ&ä…À@#Âá¬5
+óVA‰5æÒw|aå(éwUK‚è;k>GÁ¯Ûs´ƒì±bøÝH?„¸ŒW8Õäî5,K¥ñ®0ä‘CY¸—Àôž*öÏwý¾±«ÈX ¥t±ÙL^k™µîhá
+„Hí)=sîëjŒ¾ÀðÉË.êÙkØuðÝ
+Öè}3ÃPàíÄò*G )qÖàhƒ+_Ë {òÄ%ÑnÖ[1ûvo¢7Î-±¦U^±±Éà¾<]3ç´L¾Ì§ô{Eâa÷–©ûŒ¾ƒúêåêÌö $Ík/´€-š™Ó€G0 øËjÃÌý-žÎ~â÷î ­ÒVpŒá,9α[WS™ÄxI4Á2 Óiô K ú ¯Až#yö"9£¶-€º[7ÍÝé‚Ó¥“}³RƒòK™å
+
+j7 þÿºeáV¢}~ÍÁ
+÷k^_ö «†æÊŽÄi¸Í¥êVQeÏTKJªƒï¬\öz¦i²Ç²ç(xĶ0]„1Ðï÷RA¨Ðs¶‹)ã]–'ãw´·>ÇIxÆtÉWÁ
+`-§Á©3ïò+A
+‹ÀÜ£Šf®ª}/µPRGøêJöèÅï³êž®Êæ`399ã9«âþ®´ÔØ€d #Ý,Xˆ]Åõ À˜µ(ÛgDf·˜ÔR¤«ßÚŽõöEøq»›º„g‹7UæÓàÉv¹aô£ŽŽF20*ýÂ[/¶Ž² Ýr(4ÇØ“fOs$PE„ÐvIb mÒ
+l¾Ê¯b´CG&O}M¢j¯«Üa1Õâáà$Sù ­ƒ@{.cñè½Ø„s@ >¡­ïFW1•ýAë…!”¸÷`4ø‘,ñ^²y¨—¥x¯Ý´¤yË“=íþ¥E±(a½-ícÔ­ñ¶è¹Õ/pU|÷±ÉÚ†Ýc› ò¬Ë¨qJqÈ« fiÀß2ÂvºxÔ-Ç|Œ«¬í8X}Þ$ÞÏg/|ìÉÍ‹°¸„/áCõ=²‘<>ô¶dtÜŒl)Z=j]oÈÝë@ƒ'Ý—>w視-MxS•ÄýIqM5ÁP2‚½ËhÓ÷‡-i¶×á<*ýý•¥øokàiPóâÙ'*gUKB@‰qŸ×ÀÃü¥VV­ß]+®K­èo[SЬçÒA’_·êÑa#É´£¸kÊÊžÂ*@Ó¤ Ħw¾¬ܯ
+Ö}-ÝF#+üž2 ­Ç\+ÎMÙ,=Çöu u¦zÎ\l
+žÚ4°|µH“2 Sþ ZÀÕÏj&ܶ·F×õ@ëùÀ4ÐPƒý¼Jæ
+8Dè1Û…é\Úw}௶_O.ÐœQÉÔs3Ï~oNŸ
+‚¼Ã¦õ+A+òw 2Z»Ò6µÇÅPΘc© U™à‘..g*z7«_$\(mÊÀº¹W“W_ðö°RÆ[Q¾¬_¼“©–IðŒÌ[Í9ŠTºqmð©iõð'Oîʇù¶Jp¹3âko¿ŸC걂ãyÖG_ºŠ¯ýæÔÇÏïz›
+ah5òƉ Þà„5“–׺õ΄Ďãtp='›o'?þß®(mÞ¢ôX\Äs·ã¾ù«ì™h˜h•‡Ä 14^:Ýffëé³Å¬’òœ9‰¾Ûý ¿EtÄ¡ìžÌ±çXÆ¡h¡Õmÿeöo†ÂL%kûV!Za
+®`QÞ‡fpW!©mP ñ_*þïv߆¼Áaü×ÚãQÿõšÖ+tòdS—å¸A«M¥[{¯¦"ÈÍ!Œšj_ûÈwŽYís:å“p5•:Øfe8*6Ù¾¡-j¥Fûn„(– F
+Ë#†:­‡|‘õ³2¾¯a„½~6òÖ_G_ã7 #Þñ ØFV]ëÝM`<YC…ÐÀ°&_^j5&>ÀË÷e­çûÖ›àq×Rø¬µõÿ¸•*g“ÂNiÙfàÆX¶5yµë5`]c~Õ¦³mª¸0¦&A‘Ýʦºªzm ¾’jžQæ(‘™L’hŒÎ½ö w¬³AØßÄÇòºø½nÚÚÓhÖœ!0»á®^«BpÁU^Ðÿá»êV,=nàì;œ›€Hè_u÷¥=ä"aBBÀÄ!„`¼Kþ˜½plLÞ>¥VUŸ3sÎ.ËÂL>µZ-•J]™YhÏó¼À~/¾VäÊÁ¢Mw?âùÞ×ÛG„²¨Ÿ
+àÚýEÄs†g÷ÐaW*)>ÜÕLâØÈUÑÍkóÏzîVÅ«<ʬœ¡‚ŒU[ç{ vŽšJ9Z}}ö|k[{¨s(ünÄŠ™]^_T|è ôT¡)˜—*Ñ{ü †»%¹Ùbç™Ö5Hp+6¯pÒ~˜Z×E¨
+GèÞ§®Tc¯Í³$W"Ålžî¹3ƒÕ‡n€yue;ˆP K9
+š^-1$iw4& ¯YÛû僘nøwàMJçýZ¯ìbÀs ÑÒdº1ýä8æù±Jü3ŠÝÓr?¾Ë.1¹Xë+„X{Lžáz{)=©°tï¼ÞHž!]&Ÿ{øÜx!ÜJ´f ƒgY»R Øæù±ÝnÒ Gf(—ã½Ö=¢ƒñvÝ9˜éd`YüÞ Qx¡ YT£#Â¥scKеZF’<Uô¥}Üωýg$3Æi1ÎÇkŠßGˆ
+hm“ý&)½@ÛÑcl8È+`€Ô“XÀ5uõ!^ðY~ÙžFétq½Eª|÷qÉíÞ!”[ À>47ÒJ
+ÖÔª³DŸ«SQB«:~d¾MöÂgü±†x“¹T=ç7;osBíKñoÿNð«À̉`›W»Ã¶m=ÀÚ›²b£2‚ÚìšØHô‘9-P`µö¥‘^•ƒµTäîOåÅúz™eY Ys©ƒ-¦eÔ€¥kT=i§z&˦¸8Ÿ»¾}×+ûŠ1$×Ö8ôbkL Ç4XÏFÄЬ§I‚G\ ¦e§IæšõÖ:7ʽ¡]+ËÁD"(ÞÆZ—GQÝL;›êë½¢ ^Ç€õKŠA<Q%òã½E„ÏøÜX$€Ûв|ýz<5 !¯]¬%ý‡
+»<¯ΈØ "p¬ŒLÐ==ñ{ŽCß@sè 9:Q#˜wK6Ó»ƒÉŽƒº
+aUÀ^Ë ­ 9Íz æ;å %Å•Z}
+¯Ah ùßûÃNZ,¬
+9*™JìfÄ5 Ñ^¬éê«Rˆõ\Á¡¯M,ä–CË]««ŸÌemq>ŸÏ”tø]sÃíE0yÁ›´M×™ÚæSì½’`6L*•ïXÕ~ºªõËÃóšxÄ讨9dÿE°í[Ž•ÐÁÞó-ÁÛŠ[nV×@5TÁèL]”ŒbuÐÔn÷‘ÍJ¿Ý™,‘Í
+s™ë‹§A‰N‚@}ë´¬À¦Iâ–US½,ENQ B£mÑâ‡ÉQi+z‚ƒy¼¹‚î6%½A Ùý…°ê°gm=ÖÈC{ºLâ–£Ó]ôx ¯üL-@.„œ_ËC1„ü¿‹{ZW$8£«2IÖk©NU_ß™ð¤X)¯ÇÊ6l$·ýôOú>7Ò†Bçþ42§/ˆ9IUØRM`ßËÔ%Ë•¾£m
+†ö¼œ¡!ì,"î¶T
+A¼šJøî4†áQµí…0J/5¬Q_¹5i
+²"Ž ŽCQϽ_¥#¯Œ3¢Ø£>2ËdŒ6øÚû鋆œo¸Ï}è2˜øsPEÌ5H®€­UÕ¢zs¸Õ‹÷&ø·–*ð—ÿpÕƨ–*~K+jÏÐØ=‘àÚ¨Fðú•â蜓%Vƒ,ßpÓ´`»ãŸÎ>±Ê¢JílÅõcÔèI§/³ò0HMãB@}ºmµ$¼rêg}ón]¾øòòíŸ_ýô‹¯~;ÿþ›ï¿þá§ÿþóßýøã‡>øõ‡üëã+ø‹o>~üîåÃûKÿñ+üÿöçw?á_ºäKÚÿ¾ý~ù~ø7 Ÿ/íòûË_ÿ–.ïÝöOï~ÕrñÎe—V};ÊA7-eˆƒ]z…Ÿ_Ã.RÞõsëäðÕÉGø‡w`¾üÇM“/£hýmZºðeÃPø(u ®!V9 äpƒ½¦J°;ËÌø~m>Žª›ÚŠ.uÓ»
+°TB–ÏËA/^klR{öÛDUî ùSÃŒÝÏAµ3ùضþQ¿3-5yÞ(,b%[A8m•½Ö$:„d;°„ÜÜLž3e VÌbJÐת
+^ÿ‚jÙµ#R?³\L Ž
+×|ˆ• ÛE¸Ü%à¯`Rl©¬rɺÅæéù¯ÖY¶<­š-û½/ÕêY]e1h½•?öÕvHŒX€Ìš ð¤íU[ãyˆ9@Ò¢—ZDû*àO¼}wv± Z³uE›Ê÷þfOzïˆy´C ËXqøUÞ&Ú"!¦¼öì×{@9J9öÝëwWø`»¾u/q=ñÎ(IrW)€[¿ôïÑ´¯÷ØI ÌËsÕOi|gëßzÀó.gKM'‰•L°>.K 2[yíŸ}HMèíj9ÛØ—Rtn?ÀWP“ï#xÚí\!u‡ÕvM|t„SäÆÛðL“o'5‚ªC]åÊs<Ÿ÷0Ê–¶xâÓ¡îxƒûHXeÌüv7Jvvò'p:ybÉ­}—Àï4¶’Ã<v+§¬°1;É·KSc‡G¬ _=Ì9êíáמ:ý ‚´ð/2Ö!;‰ç=†F0æ,Fß·¾°^»Õ!ã/Öè'BMÖ ¼ï]†jOÊ ÊAzŒG€]jNªxþî0½TšN,T\gÆ_24A¶éßÄÏá `ƒð>,ØÒEEá@fé RÔ×Ä˱:Ö ¢`Ä:7V¨Ûæ$[
+$®‚
+
+ÙQ¯“
+ º3ÌÔ±f{òœFº¯_aŠÞŽ4tžš@nWt¤pô6w¥ ¯Zƒp@^P5ààH·‰Ðº,QXº9°Ozj~ *ÖƒB–9Ø#M0Xe;=vÂýu¤-^/AèƲx` "ÜàÓA®­ŒÌ3T*-A.Î,
+¦ï3øÙÖrŠ
+ѱn¶°¤A»ƒ¼…•­¿§ ,pK¡}ªÍ½^²40+ÆèË–š'³Bu¬t‹•™ƒÍdÆrк³6`4ø²-µƒí±âÌp¬^ŠðÛ"‘½ÑÀ
+q¡h¾X©ׂ'™«dw[/‰¤«Çfí\Æ
+6]H£ª¥„µÿå*[ÿÚtBaùª—¼›XMBë`EøEâŸBmÂAãÏ!ªOßf³€"°%Jö=¦$ŽŒv9ê ÐsŽ+%4eà£VàÔ»hÍ5i¨ªÅUZˆ¤¹¿mwýÆCªÛÛÀe/œ‡Û2ÂÌPŽ7i3k–•Äa½БñƒÞ¹NKa¨¶ú‘$„d+US2õQd(›˜gùšÀ,º ûÑ\H”—ÚìUšÔ¶³Îοîj%K —È©L\`ìy=Å
+Z{KJaÅbaU3~ˆœf’ê¡®òÍ«zØT·ïH@ìUÝföº<ÔÖ¶‡l3Õ¼k‘ ÌÛ «bX¼Ltr̳ j˜›J‘Ñïp_† C%ß6Ÿ{¾­RôÓNÜfxÑ=Ûb£¥‚f“òA1ѼÝrr@däNçl¥×„qÅáøxl®ønËÁ6¦5ñ¢S·*q±,Ü;Aۆ˭7ª=ÇQ¥Z{²¨Ý²”S;¾æ´–> ¹RÇK5Í*q'';@ÓÌ\ͧV³LRßonƒßݾËt¶ÛGQf»ãƒ*Yh§*Š w"H|}:Ya¶nl÷ô'—O1팞jN³Å?º¼Å_ŠÜ `$[#¹ W7—£ù…ÑÄ-°ÍÖ%Í $¸ùm ŽHIƒjñ¶[z)õh]nis1 N©³ÕJ ÕS·)1¥âµ÷‹}ßRŸµ¡Öëæ@ইì^­Êc˜”‚¾L™ˆÚÞP‰jšM;¼_åÑèßgz›ähÁ#}ÏÝPWøq€SiÀ›“ŸÕÉUZ±²îˆø4{›Šè͘Ҥ
+.n•š‹ß%g.ß©ü×ã/¬¶{u&ÚÕJJ‡ÐLÒ`¤Û¤œ ûL_¤Ó$=¶¶^ÀÍãT •ÂZ PhÞiu(’Ûið—¶áNXšÜQø±ƒ!5+O°3M{
+'ïѶ)”<©áç®›5ĽªrG¥äÑwyëÛSÛ>o4ýxˆàÂçš5Øt;ˆwŠÓ–Ú5´“BZuÙr ã¤%a»ã߃“´óôjÖ¸[Kõ›<¬ÍƒŸ¥ÜãÔÇe:Å; MçÕwiú÷Ùa mïiJÔCaOõœ\*¤ðÀ“î·vàõusSèVr†sVâ4'Ôša³ìÛкŽL+ÆþøëzÐgäYä<GtÜSá\C^¥?AæPÒ%¡”Ø%"•<³€¾$]«ÈÝuÉ ö‰ ‰ªº¬¼‚Q”$Ž/™Ãø³z·á$»5³
+QsSÛãõ‰[\]l ,Tl
+D@ïÇVÈY߶‚ ’,›[qÔv®Ƕ,¸š€T8bí‹éD‹¸X§Š-NÍ
+28‘X5CmO•w¥ÀýG/ªFp|‚ Ÿ×ÚÒj÷¹ì–ˆêë˜ã«ñ…ìd’1¶"šØ]{¨Á­°§Æï 'ðŠ)Jë3*Þ+É
+;§l
+´Ëál[ -wE&2:Öâ"
+4±ßš‚žáº–œ–·}Ï‘ÅŽŒÏì­”Žö}Ðãá=£ÖÖµ4uÚ_× Ü"øel?öŽìvÖ¦y(»h4iñV4Wµ¤‡Ãþú«K〦ò~z&[Tη‡Šdìi;‚ý8÷q¡6"ØŽ|šÚÍ´ o5îù;ÂöUi=R½mÓ)ìÕ[…*¼k;t…µ¢5­‚Ö Æ\
+?^öÖšøýi§­×†{é.ÖÆ¡R™³›ö°2.j÷
+Û¬Ù½Ž ‹–1ˆUzÍO¶M¶¥™÷n¯‹?d#Ü-äm˜¢[À ï)å üð²‡ËHþâ")é/1¤Èù
+#½ÌÓ‘CØ„ªÛ†«Œ]ä8²‚[›Ý^F{M§” u™îTQ¸O…-K CjªÆRî•ñ‚YÔ¦º8õ{U§t—0:=É2P©ÃØ ²!À;/ì?Ev€Ñ§,`ÌYÇSBš)t>ªm
+À˜ÛÝÖZóÊ'Ȧ´Á
+*ì`)…u‘Њ¥;ªR<ÈéÙH™w–êu…Q ©¨‰ç3DW–§k.ZèF{Ýû¼aºUª’*׃=ßó\² t­¿ÓiÀ†¦”• @ÿÛ¿Á£lWéÔµôîX“h ÃhèÂ@EóÎ[meЫeÄLª„0šbJ ?Ðy©¤¨µÌ Š õé¥ÑULVS"˜\/W÷ÇûΠÖc2ÛÊÍi‹O‘4Šc(ætŒD¿p3îìF C%Fšæå4>bá™s£úcŒDAçbîvÀý`ÒÚ²‰„ûfÉægR•µWMÆmz¿È
+ ­,2¸)<a\°ñÚ”
+E‹Œä`îôxßÔá`-ÒyouçÙ£ñ>½kºŽç%ŒE
+F°;XË Ám
+->]NÝýQC™Ä§Ü0Q1A [¹Ê™­\lVk[µGlѦÏÑFB”£XÛ7ÝÛ*ÀÖóÛOœñ~n¡“w‰kF p—©ì‚²•bæKX’’ËåÔúØÀ«ï¼_ë
+°4´MÒ(Y5o×öBPu'hjû¼·z{¨Æ;sŸ^=×@̉Té.ô‘Ìã)*ÐÖCTÑuÖDl¶‚Ö§ü8ª¾¥¢Þ©
+$m<¯’EAç(ÚN-P§Q×bJÉRéбPÜŽ š?ô|•6¢kÍP¿5 Ž’ ö<"ûæ€9â|ÎR9
+«dG×-QXYƒV›hŽ¤J­F¬Ž`_çï·í©4·§Q¤ý^>ž
+W7 {Zɼ il"í3•Õ]3¯{+hÈ=‚"µÄ!6ÊÞiAO®Î‚ÅIâìP¦7‚uDd|À>´?w¡ç°5ië° Ú,Ÿð ­p7´E×Âd»I›|ºªp§\uÌ á ŸQ—çá ŒT—ÃYX—=0&’ͳUÉ~œ/‚οlׂ"\䨶=šš)µ’ªH«\[!;†gÉ-šùÙÏ×MmšD^ «,z>p
+@Ù\h×z•ó­äå€ä[Uµ:¸8×1‡£Ü? [ë¥g[çyxZy'rd““§QŠÁíF]©½ž½¤“­´ž© }µÎƒm¥65%ÑP‘`VÚ ß¾4ýÚ×€PIû^aÆ\Ô
+uïwÑlçfu‹úq¬7Œc þN›¬”Ýöžö·Ç3_ÞÌË“§—¯ÿúÞ·Ožÿa|ûû»ï^¼ýéßÿøóËwï¾{çà‹ïx}÷üäË»»—o¾ÿî²Ð á§7ryŽÏ×?ßü„ÿÒEÖ¿¯ÁÄ—úùR/ºüíïrùÎ$¿ØÔ-˜Ï²I¶¢Ÿ½‡‚/,>ñÙ>ý9NÞAÓçó4¨JµþuúÌ›“ã§Oœ«r¢ö«›tùáýßÐ=¥çë·ˆ\)Ð[>Ü»õݧ_½¯YsÍŽw¹² µokÐ=›ÎÑ}úÕ5ß6âO`Î'o£Ø,ÄW0k dn\©õÕ»M'Mî|
+¶PþžµjUÛ¼ëaɯrîÞ»ºÕ8éÓ±àv=a=kÖ°bP¦O¦50°¡céžÂà<{”\—ÞDpP>Y°Oi¦åƒ7(¶ÿh3±zÀ£èbmȪ‘Tò€T¤‘S“d§Ì ü bcšêt9ªôîà0É°"+òÃoÌFí5"+€¹Wj§™c©Iê^i—ƒòPý¶ÌZ fj;&úƒ÷ÃåÔäõÇ0uã'[ÒcÖëZ~h««U…¸^
+ë*ùk`ìVRg:
+{)Ä«·çú°¦ÃEгV Tkus‰£jó/‘Ï82
+¥B´ê­Cðk_6[ÈDIEˆa$)2ôs¼[ÁpúÀÿ%ÁM}»ó$`#Ó¶ O÷ïjDø éœ=L+À`Š÷/"ÕРÛ0H!7«‚W«*
+¬jmËJÒÆëâë?‹¸£1ëbâÓÕB™"\‡9¹RLŸDéͱb†@Ù‚¶é6¾/Ú´lù{@Föo "í®æ hYŠÁ¤` h.¦ÊòÆvù·Ôi Í*Ó1 ì1GkW.¥ÀŽ£˜¢Û­™•AUÒ’E‹aê×IäO^Ö¢Ç"ܦ€•µNÛƒ©Õøýmž¶å7.6í‚~O>¦¨_¹ÉD"¼d
+´hN2 ]Ë©g2'‚à,JàÛøƒˆõ²%*ì©‘0(Ñ © úpljÈÇÀPw­&Rd‘‰l2·f¹!,§8`ÇÄ Ú‹ñ
+y-ÁXdÝ^«(D7¸˜ kkIÌWR˜Ž¢& /êÚ
+¿‚
+Š_«Ô“,
+RQ¬³æ$±I¯Ö•-G;0fHé0T«®•?Hò°v‰1ˆÔÜlLƒlÑ
+H‰´WËŽX· ý‚ù‡»)à
+cê6†ÇN`8æï{øÐÇv³é{¯$>Îá!èµÓHèx~óµ.^ç“ÝL§Ì9Ž4öÕ×Of[eœCÜ&g7“ãÖ¿ídbr³â$#wãä¾}—ª„qžl3<çIŠ›â
+rr¼‰ÔéÉ+³¤gÇnCn%3/'7Öã>ª$§ôUB˜#ê1èl}V@H—¤ë@ņ§¹/œÊ¢ÇÝM¥”–‡dŠD Ú1ùŸq„öãCyöDE¦©ïì/¢êñk?Ü|wó‡~óôÝûo_ݾõÓÛïî?Áôäk|f<e~u|óýûw¯ÞþçxòìÙÓÛÛ_Þüý§÷/Ü÷«ãðü3þyöd.äKq§Í¦'±y#{„—/Ž—#Ü4³×Ѹ¤þvëHI€È‹`açêè¦È ÑÙ{˜ÑÖtF?wôkÙPü•©Fu{«Þe]™ë~šeNò{åZÖn´iHZäí?Eª§Æ:Ò,gC‹ºy»Ue†!Ëa6ï@É>hûq(Woè’…ÖÙ£¸ª'º9:“Äsç6CiÆLWbt®”uH„©ì•J!’Ǩâ“gy|ÿã‹Ÿ_>»{ùö_{ñþÇ¿|[.ýŸ.?ÜÿüòWÝ`d†úÛh"A Þ Ü¯nXjN¤-šu·e#›ay÷¦+¼ª¸/.#Í•¶Ù‰“F`UàÕÍÀe*×Õ²™€Ô9ò{BµP£¨–tÒ:@²Á i‹ëX˨-É m§¦=» ‰hñð8Çà¢çÕUL=©âQD0¨@kèº< ̲ùmNº|µo#Hÿ~ûê&½ýZð‰y’’5[£2jç±bU¸0OÓ´
+þ(â&ñàÙsi³‘ »ë{¾ˆGÁÕ´ÊWŠÐ*#ÆÓljÝ°”uJåª]8š”·Q´G¨Êzø>FL_
+õ½æ³ ¶r Âq­: íáû˜ŸaÔ6õ,3C½Pƒ$j`ôø:`§LK­—ïL0!«ÒhWηÒ%,w\ê.ý2×@3ûj¡½jM«¾g&]«¥°ƒŽ²`ôÏPË3QS«‰Â'?ï×çàEJ:%H\I.ðIZalâFX©_
+ÞÒbc<…$åˆ#ëúÚ ¢b¡fóšž®[ÚÈRq—T%óâM»ÅÐ-¬B ÷‹Žô"N­Ñã¸^VÄÙló‚ -Ö¾·ìd¢)L6¾&ý%‹Ô¯ªï7—.f·UªÁIæ š¸ô Dµ(¨­LŠ0ï‡"‡,­f‘‹m¦+«¶k—®ë¶§ Ç«VÿhœÁ âЬÀØ ªh«ªÕil](\h…,_ØhZÍybÙ¾mŽU£sÐ^í>iÛ/wÆÝÍ¿ÿoeøô1•a´ñV’Ó(9ׇò›ê£ÉzñóÚ˜ƒîà­ð×ÖÇÙ
+«­»’kj9Óœ¥èÑKÙß#EL»íi sà.ój\“#îKãH-é3²2Ñ5…J¶Ã°hüÎç²\LÐnØNËfiÕ:µQÎÞ._çfª®“/£%o8‰¾X2Sñxç¥'tÄÌ×~t*P¨¡²W­;Û—VÈ£•D—¶±V¡ü:ÕÇo_•Ý­ïü¤K¥^úy%¡ñ¢a‚S“ïÁ×>Å7ß¹òOéT£ ®DTÌ\«QN°tÉ,(éju#¸I§7Œ•!h h¬gÝï»+Õ )‡ýÃÀé6³èU.ÖbA‘zø4F½ƒvÿ¨·ÁØ«„ÅàTWdÕRÐà^N«ù#Ó“Fˆ» ‘ m— ¨ïäÉŸËUØ¡<4®ŠJë\´Gîº0«/é;dÛ¬ñu,S&,½Ò•¼?B;„/Ø~'xIî©^ŒÔ±0
+§~DdÔ÷>+çcõ
+&4aZúÄÄkÔHžqºu™€­Ì#éÅ3ÎÌ⊃ÄÁl¿fVjÀýj^¹ïéu:ÄŸ€¯‡iŽW SËDZîojµ™Þš¾H–¶Í
+›ïœ:¯|r“0ƒžŒ÷ÔøäÔ•dD€ýÐÃ8|±½4Ir 5gܦFMœ 0ó©2Œ³\AŽ… —Ê$\¿öÎì w]ò~¯}±Œ¹o¬Cw{úiz£Å„ù7nƒ™ê½XòÆܾÀ¥ñ0ïë ¢„¶àÇ¬Ø öüoß@UÁ}ís?IÎc]î-‚ÿ{¶” ®I§.!³¥tï•®ûVÏ#ƒ$}´HUÂHRLBIL»ÊÑòÜ™¬v¿e‹åî*s韾ʩr5¸¯7`wœ9}fÏQùù{ïgÌážèyÌ9Ùbš@fH=qÞgn`ÝNäG ì[ß³µtñçÇþ¾ ë’–œ>Ãðµ’KyLW4é‹ÑÑÓ÷¡_Åy"{‚R4ø{1»ö¹¿ùb*vÉRëcÒÐüë`FákK\k•kÃÜ,QÕ·LAcŠä–ËÉÓ™a«2°ýPÏÙöBª²#þ$;Y¾®j̪F±oæÁYi£Nf«L4‹:ª¬?hC¸bÁ Ž-|$“‘8’f¥|<D… ØY•bÔ\ˆ®ÄŠ“ùu¶ê2s¡´Wfrκ+³rÜçÒ¹Í~L½WpŠ5ïøbÄ•á_jQp¼ÖHŸ$‰`ÿkYYEïu½æyφA{Ùd¶V=»…1ûþ@‡¤Ñãy©³ÞZ­_AoiœXûò
+ çž“Ó"K‰p ”äg6³¨Ù˜òXšØ÷Ó^4£Û î³½£Š6‹½ƒ¤Ph ›ÚWÝ’ñü¶û)EäíxçS‰È/¿s/ŽDlä
+úNU ¥ôsÁXêÕãA\0}ô{u ƒŽåT“½-ËÝ
+g ”%ÇÄÃÝ£É ›ðs­Æ0ÃF‚fWç~DÅ2
+Cl`~¦QWxwási‘3Ff[—¿äêt}ë(³1¼-p‘Ž
+½1 ±ÝçrÛZ ˆÔy‘KYbÔ•ÉÅ<Ê!õGŠG/|·qX®ñ®—È4KùnäQ Ͻñ´ 0zL†¾ØÀ݉ë ø•Ã—Ï{m Õv–U/1Æ&‡ªD(Ó6ºú¹[R]±‡/m\ò •&*b_eM”O¦c••8ѱžšðBoGØÌn+lwÑk#£]UéX]­FWmJI­nÿ}ñí>"˜…Ò{²P)Ða%ˆÂüœ˜PM/Î4| °¯î®XzWÂÁ”ZJæýK6 2Õ/U¯ât Mùœp›7Æ[ia6jéxÛkË¡-ú˜®SË…¥+É.çºÄ…ØM_5ñÎ}2ã ø›¢¤AǾD óO¹§zqbê£GË^MK”Ç8¢Óòk‚¤§— Ô>3^k}Þ8¹1l_¢;¿iïäCï¼o÷@"]öìRûÚ#ñå•Rô~ãõ¦›*ÕŒÕqÑ°ÕHB…Ðj=àᕪ‡Ù¦2ÑÝš°´¿©}eóAºï1ÖzÝÕý†F@‚ás-îîuSéJzc’¥–¢³/òæ²MvzAÙm2¨bÄgŽf§FpªI«pfå’Ð`;XMß]½ðîPâ °Ë®°ß¡ž;_$p9úeHÞ öÕ%²‘žû]‡ð:ý¨Ö†ÌëiƒÜÝ|ûaBÒ½þ^AA—R/3>aÂj´äÝä¼@¶áÙ‚—1/Q¥R¨Ïyè£uZÛ%ä¢[Ÿ__\+åä`ÏÝwˆ^Z>ݪ&âEAb{(ƒ¯üãAìSÁêxëUNÙâã|òïªßŠ©Ôtºúú[¾ü,­:9Ö”ØÑ’š· `,ý
+¼·qg¶«8OJ¤2àÅZºyŒÍ¦kcž -xë4Õ¸Ñjõ›À Ø°Öº‡¬ŠXln•dº‹WµÒ æ…øÂl Ÿk^ÝNo« aB¢—¾NàÄ›×Æm©vÞ¾»jâî SèôÇÙÈ?É/ÒÚHV:PÙ¦JiÍw$À•Š›rÆ3N–¶¶˜ÐÇYÊÕƒ^—ÅÁžÖ ù{ñ}”)ÀRí(&ì;Ø›& [Òš4ñLyj†*†Ýo¢ ˜*ÆØ2ìM¨ó^×ÏãªqN”æUTü*mf›­Å¥+‘àóàÆf€óð£˜WÝdá¡‚ƒ™ŠF~¼uÁê=µ=šÎ²xÙÙ<+™|Æ¢æ D×¾Úm1œnö~PG!½?Íî­ºåuúFèFÖ›£-úF#I-d˜÷¹ÉpãxøÉÒÙ¼º×“ÉÔê­¯yG +9YL¸%}S£Fu÷#ðÃÏ"SîP§œìÝïO977
+æÌ]§,ËHA½.g7jK¨Û¸ÇE\ŒØ"ãȤK1ºñ€ÐsôÅÁ!y @n:zV<št>æ²a/LäBÄ×¾4ŸÝáìLexb1™ÓþξrÖyâÁ‡#z íuñ‚©>®YCÄAn5‚åÕêÇë¥Íê –ü¨½´EcÔ _Q܃¬3—ÆÉz°ûÌêŠ Æz'Ñ %¤gÎÁQ@¸C2¡”:Ҽ݋DµPŸ"¼ÏÒ¬àõù8©S6 ør‘x.Dë
+/¾ä뻇œISc¹©<>‚ÊædfíÛÚk ëªf™h‹…Öcy¯«7¯Â‰ê i‹4XZ ŽÕ?WÝ«nótÊN
+Öä×Z ‹;Ucôy¡aœ ç–¯{?}Š¯~IgÇyU,¾ ²ÄyÚñ I[æý¯iŠ–x6ª­#nMÞ
+SI]ãú!O¬rc©ÂÃæ<; 1`¤Ó¬¾|´w ¿R!ÎGï¢qÄ]ª1øÜ%¦­ãæš°‘pµÙW™‰´ öïæ‘L™ÃBP?êÂÊ4‹8J—`‰ƒ
+U•ë
+y<ÓãFyC’]æôñ'›G͸jfh—zAcEª‘Ï;ðN½?Å–Ë:x¢•¤È
+¬9rŠDªÈb{`@{±Ï;,wÈ
+' 0»nàËÔòæ«0iv@fVeÛ\0áeúŽ8Jb×'d\\2ÍãËŒ{òôØŸ(wüX°"$&¬dùd̃ŽÁUúh¤sûZô|V`KÞʬÃÓGj:hQ5.TPëêiŠ×ÓfNÿIQ¥ðhìFBê ™àxrëTâÄUT${
+!_êñ›»a¸²®½@à tâF’² pÓø+|ñ$–ä«NRƒ¬ã0îYCÄ1ÀZÍ”ÔabÖHÄfŒ'‰ÐWSÃÓj©U×"À”œû±ã1= šRâÌÕÝ®lúX+ñ(’£OŸZpZ›Z² [qöÆL$,VNKŸ»œÙ5@¾þ²'.Ð(˜ /T_òÐs< pÚ‹99i‰Ñ®VVÁ‰Ey@4W¸z“/G“‰
+‚ý`r†Ix;œéÀnû”VË‹¤D·ìôz‡<;¼Ñ·])JR&‹KB;¤Ù´>l‡K-ào×}Ûa¯Ùr•¤´’¼HjJÃ4«òŸæhµ ‰¹ i·¬H:TùW ¶?[á•™i´§kË.(¼†Vظ ïœý“!¸ïæÐ<m,AÞ›/wdVѬDï:¶àçpÛ>,0¹YÕ‹=N¼›™<8útuDW tåÚáCüö‘%ákJJãˆÏ®ÿÀIyœUé gCÍ¿eŸZ¬%¸Ã<½ß-L?|ašç$=ÏH±[™ìZ™,!ž´1vx³Û˜vÓ?9¥wÓnczéì6¦—¿ۘØÆdɨßmLnL§¨*
+‚ÒcZmãñLuÔ•nGMô5ÇÓÖ¥ëáX±.åìßÄîx%ÛEòFZüb>/°)g–¶„=•ôÜ^Èêè¯!%+:bfr–ôÁ¬'äfFõl ôû wºÂIEQþ™©&@¿õ‘ú%#iùù óEØØ÷ƒÐq;r¿ùât
+`Ÿ‹jè˜S«((6ùA9{$ÍòŸï¤ú7[×t5üŽOU>G ž&ÈìZh‰-Èwø ûå~•¥85Vk iôq?îׇ,-Jô¿ìrKÁÞH‘|È–«¬n Ë· «(íÿ˜—I꙼˜fUžà#ºZ ;<©1·ÝÈV8Ge64Ç¥/ƒ…·eÞSS7•tš£Õ‚$v$â¹­MIù‘ÁaòZzÛ*†ú}rR.–¸´¥¬ôþÖýù30yb‰¨b@;Їt‘uÐÝtÿ•h>´Ýض@÷6œeg8¿ÙÆ{äUö××9A³ù¼Àå1­†ñEÆç¦Y½ñ·kì?!ú÷'Þ GñÞÄÏùÉŽ
+KX_Þ¸ûØRþÏ¥[´Û£ôs
+ü1Çß*(uc7;¶èÐê¶Kiµ¼€Ê¿ÅF]Úá¸íJQ’2Y\jö%ͦõa;\jßxàYRe‹¼R¬(Jð§åZÙ89þ|ëÛ@î¥CÅm_ù3¨÷®%Ñï‹Õ‡ ”¦˜N1ÅI½{®÷äÈGú°ÛnÝoèÖ¯v¸Õ‡Ý™| ¢F‰kI­q¬ŠÍò3‡îmOY0–ëпŽÕÂ’zö¨ ìÀ?0êÌÃÂüýaQ0jng$þŒz?ÝÖO;ü^mÄT§¨*
+‚ÒcZ™†Ë *ýq” ½AÎ ¥¦ò˜çB¿åÔ7xÛ¹•‡ÎkÄë)ÊÙ ¾%ˆ=g‡KøºÍ³¥É%Kj~™™À‡v€g@ÛÐgFšØ‡˜uËàk¨mô å’3ømr’£!G·ì†ÂÜéï$©–53è~š¥–𔎹ãÍlFJrkô%Ç+Š[Ü‘ {ú®D¹Qà!z‡,i ¹íÉ5¾1 mÇ8XƒþÙX <aÓ°Eé>wÓ°dÜ<{Ó°Óx‚®²M°´!o´L\Ìç6UÛ‹ú'ô°Ñn '–TÏs[Øüýž=˜°Äv`gH7ê×Ë*¿®(NõÔ‹ölsÃúÐÏs””ˆžg¤°DÊ7bkÊÿ¹ôãÙÿqú¹Fþ˜ão”º±›wŽ%r¢»íSZ-/ ôoñøVºÂ±ÃqW%‘2Y\jv&ͦõa;\jßxäYÒ3ÍÈÛ`lœbEQ‚—±_õº Ã`ð x–J ­
+CF‡GºpAhQdvn›·0ðŸÝæ øÓm*¯KgŒÄÏëX€‹’:¹OåD—ê—há«Æ—5ñžµÛÅ­ûÈÖ=—º“”ý¯kœø»åÓÕ*w­ŒúÍ÷£n¡…Þ¬`_ìcÿbžòµù\GÁ¿›äàS6ñµ£^f¶xM‡½öË°#w$¥ßY˜ûÜ7f\Qù0¥=ã‰Ñu•#¢À[Þäˆ, ¸2§ƒ£FYíÒqù±T¼÷žWíOh«/¯?
+9õ1å’“’x,Òj£¡…-Ø[èˆHº´'ÉÔg.•uìïÒS*‰:V<Ɖ1œ_Ì—aŒý·»*ÅDk÷µ•‡AÝcHÀ”5¨¢NkPv‚²UªP¹¼Â ‘³?=Å•°ó“µ'„²ödí)eO Ú,ª=õH†Œ®±¸±Båu錑øW&šRHBÎ¥5vwuRÛ!ùwȆÐ:¤,ì
+H‰ì—_sÚºÅ?A¾/g&w¦=@RÚNy
+á†éL“vJÛ¹oaÉX=²ä+K ôÓÙ$Ä2˜ $qv?´Mƒõ“öŸµ>ÉcbØZí±Ñ\N[‡³XHEÙÛì/Ib÷Ò›ƒõ¯^aó—»­ögiüÍ<É_kŸhMæ­Oý»×ƒˆ ª™Ì_òÀp%‰ÎjÿïüË…{Äýxr!
+ûV¼´|ì"—¿™ ê皉JÚ}0jÁPÑí ɵúò"‚sØÆ
+.©"­ãNs©Æ‚ÀÆVÀƒéP*‰¤ý‹šK4”rÃ+ÇTa˜%‚Xp–¢}ÍRC´Xˆ¸&s$Ͳ”ì“Lض×XþFlù.FÌž[\Rrg¸WAøN qDlšr"dF×ÆÊꀈ$"8®b]U¥V€ >©¾‚úÛ;×ûfǸ‡‡`5䥆Ù'Ùû!Ž8 |É;µõ‡¡MÙ>ŒÃh"Ðòh^Mš3!Ôuª“}×™¬ïvŸªþW‚™¾f´¯4‘S$¤ÄM¹¿ ”PH&ѽR‰_jRš€Ô¤W†Üݤ4¹“Âhh˜pß°©}ßÁqÁ¥™õ‡ÇÖDàBüˆd©¨§Gȼˆ‚Öža—.
+ˆ}ó.*ˆ5‘×êÐ-÷±³< èá€ð%—Ìòâ´O•Ì'28'p­¨^×-[] O±³Uþ*TPeå;Y„qÂçÂõ—›©Á&x{SòŽ-ObNB.AbUTj.ƒ…^ÿ~s U–¼hwùTõ§š1Ù_TvßmiÖ¿âJ0Ó׌ö•&rŠ„ô–ªdÁn»È¹6#oÕíu{2 ´@àÔÒí! _tTŠòÝÄ ÎÝÏð½HFÜÇæR¡ÝÌÇÖ¡TIË5—h(å†W±Â¨K„s7Hp–¢W‰!´WD\“9’fYJöI&l
+G©†±å»H1Cxn¹õÃB.ye?Õ~#Ká;Y©!Ï]rî¤08Š ÒŸ<2á4do†(Þwp —Fî[ûü#¥^ v¯Hð. EâÖ ¸,LBéMÀúj‘tÖ­l&µ:të}ìLhº=$¾æÏͱÿw–8&ÁkBô¢{=ש’ù¶¹ÌUÝ>×V¬`F«vYÍXeÙ;†S'*­ö=OëÀÙŒn²½ðÍåD2VVìD$ÁA’kö1TÂ41pW`Íwz_Ss/*i¤Iñ
+©Éo˜²’º‡j†ƒÉ#X÷h½N«‡d³4o¬úö…­þA{ükt–¿¥<†ò_Ö>ûFĦ)'r l_þão¬¬؉H"‚£"öÍ“ê+@RC‡ìŠ“ì3 $óÓ—¼“ù†)ƒî iK੤}{A姟 %Í(¢Þ^ßtñTE¼šågBKÒ«\:é³&K<þäÀÚw[„Šu S3Ò$‰xðl®
+œ|¾G„ªëË# ™äÒÀæÂ0ýSr“‚+jò›f ¬¤î¡ªš
+L‡‚KFª@kƸÓ\jò °±ð=”J"VEÍ%J¹á•Cµ0zA*÷UÝ8KÑ>Œf©!´™D\“9’fYJ.“$Œ˜!Üô\Rrg…\MAøC$Xo~çb7G¨I`ˆ¸P<Er ËO,æ q]ê¹\ÓcV€- ‰?·j}†m¬>OSRí#Iu•ÅÀP×íàÀ)*öYRÃMýà†‘jœ?ŒÉ^jyú\Z&S
+Œn?Ížª8QiõzÒX¹Å¨Årî+£–Ë*iKñceuÀND,Ge •0MŒÚÂÊà@¸ÓûÐ'Ÿ/džà8ŠÅ,ðÚ ÃôOg8ÁÙ¬&¿Y`ÊJê¨&`ã ·‡,lõÚã_£³üM¥ë\|R SÏŸ·?¬žXáL
+5qR\(ž6© Wjz‡ƒåBš&45¡© MUMhjBSššÐtïÐÔÃÓ„&„¡iDlšr"ÂÖñå`<›{¤Iñ
+F,Òg; 3¡TWO2¶ëà@ …díÛ´ˆÖmã¹`A˜ÕÐÛœãÓDàŸ~kñ+•€›ù§ÎßHÚãg§9Uq¢Rn c@º¥1•×ëXÓòè¹åÒ}•0MLe-‘* ±f„;½»Îá4"T]Ÿ"jºã¤ò0´)û§‘qâk˜WåïO ‘&ÿ~”ì‘I¯jß'_ÆL8 ‹¹ã}BA°OBþðØšâè÷p€,õýøû{ⶎCØw}ÓÅX±¯KGñ/û峜6 ñWɱ2”¤¦ §”Naz²k*K®þ¤éÛBØ›Œ»&—$ã±wýi¿ý¹‚ÓN eSJ⸒©Prhgõ¶ï•,%º'0í¨®JËA#1(͈d ö}_¬ýüÉê¿Ÿ:ö´-¼aM F ú‚XÇ!ÆQ¸Ö…°N‚°Ìj-MH²ª)ëÄ åõ­})”dP
+ÈÎØÕ]í«gôµ+ûB,éFóB™z/ÿ—¢»n³øŠærs2UNSv'ŠŒ`8)5‡6TÁ4±ñ”×ÀŽ-¼é=§p/OÒX“"ãcmã-¸ýNxmWö%Û OSg@zïˆ\Ím–3‹r¬6ó½éç×mÌ 3z¡[Òë° ½Ð-é7}yë7;Ò£F‘>:¯.Ã*€3¯18ؾTÐfjQe(Ò[TJ ·¨2éM«ì‘é§.j¬â©þÏ~¨JSÃìgá´fÉ(2‚1ýe•6~瀅
+H‰ìWioTÙýþï HÃóÝê.Ã'ÛDH4$¢(BM»1=cw£¦=àŸSË}^4#¦GŠ-Sï.µœ:§îúxqþfyz¼Y¬î ßíí?\ηËõj¶¹¾ß{0ÜýpvºZcÑl»Ý,_oïx>lg¯N?ÎNÏ'˧–âÔOÿò ŸpäÝ׋Gç«ùã{Ãþs,[\îã_«ÙÙ‚w}üë/|öÃþ“ÕöúÇíÅ[ù¶°ÙÌnxÿ鯺¦'|Ö/ û‹&÷ÂoæÞg+ÐO¼ÂÕª|>’± nØ‘H~–ÿ¥ßK~p;ÈW@é«‹º\Ý–ˆÇãÝ|¶+e]®nu³xw~º½-Œ£õÙÛ¯›GËÍŽDÒýþ*và0Öï–ÛÛ:ñwaˆÛÁ´{Uø5xz¾>ßÌ»Ë%¢®W%ÜŒ‚Nûf9ßXÄëë¬ß.6³ízs[4Ÿ„á7áÒ߯jñ§‹ÍÉ·hïÜÊÿýãË®µ£RËo«×_FNR̃ÓÓÝÈÃ
+ÝWçâÃmi¸ïÝÝHÁ‡›\vñÇñýâ¦ïï—ÇÛ7·ùïiWü7w¯Çðf±<yskíNÝßÒ~|[O^þs½>>ÙÌvEÙç’·<Ý.6ÿX-··¾ÊÖ¯~ZÌ·‡ëóÕ1®?Eß8¦k|–æö.^öî<ñîåŸVÇÏ|üH6³‰ØòòÙzõ7D´EP÷ï«ùpq²\]ý°÷ì­!Ÿž_œ½ZŸîÝ=DÔÃßg«“{{n8ÀÏ‹÷{çöÏ ÅþûyûûÅþógüñLï‡4<þý7óîöî—ÖòbJC äFO!gb¦1·ÈæÔÆ@ƒ®ôÎy6ÕÑÅĘ°©]—½KÃ\¶Ç1ÅBvj+qP£ÏEmD%{zöÊR?–èô®2f×Ä+“oµŒøaÌ84x¯wU¸ce³‡3Uü*mô5z5:D(FxE)󩈠¹:ñ¹`­šiløÆk3î-Ž£0–Š㘃ËBH!¨¸¢I <Ì^—º±y9Æêr6cò•Ô˜š³ýÔp­Ojö%‰b bÌ#¥hûc‰¤¶Ôª]ïF‚Y Q«O“ #5ç`FçRScòyÚ_J㼦4¶¬×#T—ti°`IñD²{pP¶8ë˜ðK’íá¿ãbcEáÿÅk ÊM«‘ìÔįêÆŒØU¢$«âÔ”9ƒ1ÁAĢЊ(ÖŒÂú—泺`C[õr¢
+¾Ö›È„™¨f[]šG©LÐLÞYbC ñ”51Z—_li }iu©ØR¤!X]ƒ¢å‘1]<ó ±rÓHŽšÅXûQ‚ê TW¨p&)Ì@fÑS±Àqˆ±RIºÖwjóÕ=3¦Ú÷\%*œä}¿«QÕÞŽìˆîO.3'…Úl?¢²Î ti_³¯fK¤Æ0æ©Ù$’~¿sÅÖ—à hªMéuuø(_í½ÞsÜã4 )¹©àvF`0rH?÷ó[ÍÊ2§0͉¹DíqT«{ §ƒ^Z9Alôh1©Œ1ÌZ´¸i„q,^;•D¾‹Q' ~ì&Yõn˜jmb£ìµ“±¢
+†—À™¾\1Ñd2#ª>b©*Jù@cÔÍ §Z‚Ô‹R67vh¢³%ï™óž hËNyà4$_uz%7SH?q~72‰¸8‘n(”Œ‡úe0D«QRà›ŒÇÀþP:‹
+87E/†±€ŒY£!¯Àe\n(èª[šrs¢žÓ¦:È,Üìö
+ê’é
+^¶‘¾‘ÜÖÁåÉ ë‹N*`†š-',Cyar”Eh
+ÅhíÜ‚h÷mT·0W•J²é¥Ø+È÷êpí'!àb Ý‘JgÍ]B(TÍ5V‡8 –¦.è)Z˜r«6ÿÄPý4—Vë8È#”™µwÐE-uXQõ†5ªÓdÊcGëI ®;ºÜ÷ûd…–®é÷c„¶¢ÒÍ|õ%4í+¤½sétL¡^Ö¹™ê¦6ÝÕ'ã©l=‰ú¸Ž&š*HtBžAÞŠe”F š¬Ï…9K:U•Ií
+¹NBá)Ú\×YPxA¾E²b’Œ Ï€zošš“Ó.,®O«|@¶¶EgÕ×ÄÈIÇÂ1x¨”É|ãÖ+Jjêkð¼ë»Öe$>YsvajÂj ¤ÖºÒß‚9¤k2†Û„HÏ:=Mct¥«Ð•ÈjƒÒfoEºT±¨sÙ *¦TH޽ȼƅöY¥¢9IóRxÒçW¥wš^GþÌÓž=cOäø’Ú$ÂJY˜‹Z¥.·(½ Ô¹haÿ7zžï¼Ü?Øl.çÛåz5Û\ ßÃt÷>‹CE'ÞöŸo7ËÕÉp÷ðð`>??ûa½ñÚ{ÃwXù
+ .§QëçŒ
+ié[vW¬§×®W8±€©ô¹PzF¿JÖsZoõ9cÕQÝŒÎkõXƒˆ¦­¤3p³À":{8]¤‰šÂÖHŒa–ïÅ ôáëÔü¯€m¡+c¥ä«xáè·a¤íÔx­Ä8+¼qEšU“Óm`‚kj¸Í*G›ÊE_6úwúÒË~qŽË÷:)œL
+þ8„"=O³Û˜#!c:
+†ècÕ8S”Œf<çl<ëIÞºrŠ»ÅÔÛí*ÜJiÇf”‹’s²Ýo ñ,ž®GGH«Ñ³ð¯û,}ií·ŠÖ@#u± n ®½™k÷Ò—…æV¬¹JubÖüδŸÄnSÚ-NL/·Ï'\©Æc³‰Ð°7Äýzõt=»F:¬ž…}2ÃÝÙs‚pÔÇk€äbi|«0ƧÂY…IB±
+CLØ[‹¹»’À¢VGÖ ¼´Ç
+q¾káq´“Ù¶Ù\A»ßˆS1Æœ81
+.B”¸çýÞ’ßΘ›çñ+s.Ö¹ùs}ëè_ì{ƒ9…!„]j¶mù–¸€|•Å®Cü×VP¨ƒÒüz|ܬyµº‚æp7`Ä¢î†ÕÌMR‚Ÿ8Èo—³3®Åê9Â^]MÉ‹.’×È“|X+æÇí#̔ȆTµ“á^†ë%ôÊ2ÐÈ#,ëY‚Ù™7£ f÷ u†"FOјS“™W,wr½ÆœººœA-VÏ Pæ ovêf +uAÖ.|‰fšsTêŒÎÅ`ÌUR’Õ¦®Çâ
+s„)зÊålkhU~³9 »]¾6!ºª‹ Ãâž·8ˆO—³3¤ÅêyøJþÛ‰£\N‰ÃÔÈò¥mð¦(bL!y%.âƒî"gÕ¬$ëê8‡³Ÿm•Únu‚ê€|?Ð¥œ½q§Fsº¨¡´Ý;uv9;ÃZ¬ž1 Ôñ„Ù¨›ÍoP‡‰¥¤Ç@yp§phPèÙ¦D‰
+o}Z¥å˜$œ3 Öº‘j9ª
+,.€7;ÜrÐÁ$TUK1°é*•hW9&õbžF+îµ°”‚ª`«,
+Úº1–¦>-´¥X¼BQd.…Û­
+Z³:ëÏi(zTšÆ)­)tâ3ïG{º9Q :hÓŒµ€º)¡b5Öf‡ó³zNÐW½9À%Æ«»Tÿºÿo‘füúY§:wϦ‹ ²¨dDQØ£´Y”É¥ÉxN:î‘!³Ù-zN–™Dté¿9(ÕIÀb`:Ýš×T)ÅayÔŠ2©Ã ˜ì´ â$›×’›Ùá-ƒë¶VåQ¢Íš3ç÷™1ÊP»¹Ñ: ½§Ï)IEsX¦AÌ3GAžoQ8í(ІñÈftjuY•Äyg3iC€{êfNQ‘NNê– Ub©´¨žÅ°ç¨`®Ñ6¨ÅqåÈ]ó¨];‚$ºOÅBpjÕ`a}ìLÓÚ*ã=ɼ^Åišþ•M±áYó°e ¦:}HpèøŽ£6Õ©¡¢»™2M];×fÍ™ó'8î¿ Ÿ Îp¤€þÂá“9ôX½™Ÿý×E_§ cTÌŒ·µÎÑiÔMT]–#Ým-ŸLT{t ª‚µ™ˆ ɲ'Ó—=¯$¬ac±JEÆ!¤L´¥6£,˜§àI¸-v)8î3CʺèOgÿB¥ Âzà³$TçótjßÄníc}íJy§ÍŽ''÷®c·„ k¡}ç8³ˆ ++Ã*›9|Ǫ4MªÊUkïÒ]‰+X3ÃI×Uãh®
+`ä
+vùM7¦´§f¼Ö 1HwçµÂ:wFY7È–8;Ï®‚JcZŠc§qFks±ÎV•}¬JÊxc*Å´KßqŸÕ…î±ßpØ#rk®­ZS<j Å+ãܘ´áÖ((S¹Tj[Á”2oàd9 Ø}™*jc£)ð[<ßJÍ¡¶È>aͽð–:_¼ëCýÙ›!vIÃÓI'Q­ÐùI˜ËꃆçÒQ]ƒ,Zö3ûÄZo®sXÃu\-“ñŒ–K–™¢…•‡¦•è´@ÛA–²3‘7f ­Y&ÒIˆ Euè_-Têc5£Y¥¨(q£ÝX§ªœe0¨E+c÷y<‡R1Bf Êœ‘™ùñü°–öZÃÞ8–bd!J®ºKÞqŸS#Ý/Ö¬f5ûR¦­÷Ý|mdÿŒêÆÛ(k‹7;Ýu#*|k]Ùë3„u~/Ù5¦Ý_™Ê!$ΰf¢ºéá]KKæ«S¦ñ-ur«&p¯4”«èZq#ÇÃn¬Ç}
+“˜K& MœÁ=s!&ÍüªU¬à0Ãà EJsëcgD×Uf¿®œ%’Ö‹€ †R…-]!WAOÊ“Ö£e£†ù£«ØÌÜ -6ÕY6gxžÚù1K‹¯J\[jfŽôL¿N88îSc.mˆ‚:då Xg´š“ð´º=Âg :ã¢Òµ)\·ì/¹ &6¹
+*}
+ŽûÌe¢ë‰N+8XQ3¶Q:¤úȲp:gÁz£1Lûƒ²BÙÛFtäl„qÞ^ûA`+lŒSŸÁ£•»è`µŠæØöª˜åÑ Ð‹†=2®È¸²æž‚öi°Ù¾?!à¸Ï‹&âPHM… `ŽUëSH§àé [§!k¼êwh1Â]†]©)U¬H!SУ o•®sÀÙ ðZèˆ;1.H§êNŸˆÙïQŸÐIߜ١ã [mý
+
+"(₈è$ˆšÄ ‘ï©:§úÙeIv¦öí¯êêSÏq¾– t›&´{/l*y;-i¨4"ZÒ f-)öÓ¾}>¯ïº ×½«ï74¾
+Œ^Â,¤¬V
+=¯r÷ªwÏñU7¼;öðÎŒë‘c0
+
+Si—éOïÆuêL¨wK»ã`SïèäÌY¹ý`þ .ÖÏÞcßñgêVo§…YWý¡¼{ödš+ØJæ:IàžÕ#h;é
+rÕjA¾Ù}ºÁæhú’8`Á¯—YW/Úºì5ç·H¯1+>
+‹ÙÛV„U‘Â×ôu³êI «rÆ°KŒépÐ=w^ŸëR‘Öì-[7Y5q-Pý¥í÷¥fg½œ{ZÝ[Øšyÿ§†!›n!WZ§^Lm¥z‹Ìü%šá!·š³B?¶\2½² Æ^•¿l¢ˆji[NáÖO>«^y§ö¾ï˜'æpÓk¾ ’¾ITvÞ“Û£Ñì& 0)< ÞûãšB?=8Žb;ÃÑÌŸü©Wb¥($ÿtÃÃqütœ’xW>ØÚ$Ceô “—÷WµÑMXÈôuîï*×GQtî½ÔŒ•t%cM»õ³ô­ãRÏJ-×E™•–EeM®(Üß-j§U¸âßvSþÊé[ÁA؃i„ŸL
+A⦃õ ¾„‘¸øáa ‡'‘’“ΰOÚ>–1 ã[™¹¾%'w»þÅApSb}ØRMLê<‚A÷oº?âŸ`“4ýN)SÌv¦"‚($9P¿WËÃzr5Æ7±ÿžõGüÇV{w}6•
+z¤àTv­w‚ÝÄ Q-Þë_¦‚â2Ä]ÖZ7gb
+¹_¨…îo£„4^t ©UÛFtÎ"ÂêPÊu9öÎIíZÙŸ\n‰rƒÜàgE·×·ke²@”;'(EÁÐᜠp#²ÝÄÍ\gÿ,ϾR¸[{×gm
+g oã9/™'OÂ{Í¥9^íéiEÆÿ¸X˃‚”—š<¤&%¿± ¦waütÊÀBì½r¸Þ凥æNlCøüîjÙ¹3'‚Owø7HƒÄdܾVÙ 6HŠB`" Œ¢lòöTwUûœDB£h¢Ê±?ÛÝ]—m<,Ô³ê%øxzËVË[ñžÙú¡£p·¯‚GßÑë-q_Ü^˜[³[ú‘ipL1Á¹‹ú¹œ±žš­»J³ûý²W¶ Ü…Äò¢Þ &%ØM bÒîú=C7=1•–­Ó¨'hèð.<À>ì}VÞ
+÷×V
+:@³, Ó¸ë ×£ÈÒA4ùä´
+òÊÛ€Öwµ> #EÜ>#ùùL8UWõ¡õöM|ÀWZ×iýñü³
+µ¥J—l™ÜÍaÄ;µåópݼ<BC¾S[T9`íêÞÚ(ê2cVO.ʉÖkBö£yMgä,¤€v×wctÃØú“jSÊ,À¦œ³ëÍž  Ÿen€)"qÌÊ®p°Ðñ8iqQdÌÝ1“,D°˜â$F¢ê·ª§¯6ÊÙ¶ûK÷åéó3C愸é·`Æ»BÃí†×G67ÓØ¥E»–Åg]G— Ô
+;ø#ÿº~ô-õ fZÙò´­ªè­ÀYëÝÿh´0ô²ÕÅ;øˆÓm|UôîHW„¬¦/gË\‹Ù"¹5Ø"8OÓÀÞ¶ësM@mŸ¡AvoLcò
+3fÇTÑ¿C^&Ä&º.݃$–ßÿû[‰à‰Kþüù«j›ò;ÿÜ‚Ôî%Ô̆$Ÿb°Ó„]gÎÁ’c
+¨+àíŸ5LÉäÊÝÖb¤7}I´a$ç$§yÊ*(°†¡®0 jÿ
+ÊQTkv×/^pMÕz:%tíC’GPp”÷ëZÁQ×o]†ÚïÑZ}í»9yy{iÕ/0¦ H¤É^âÐ÷ydº¿ö…ûÕ·÷ki
+=ÁV|è|0—)À\ÌG{:2ñ*£ûÔ«OaÎ
+æsÁ¸Ÿ¶´ÖâxBž‡ÀUÞ4 EaJa€çèZÏ}4ª¹ÑŒÇíõÔwÀtËÏt<žqƒ°C¯xÀy fn³ã
+umPíú½i- ¯?f;Qß4´' [Ó+yú„w}½¨¢¨$È°©8b>cq€Ÿ  Jbèþ’Ä6u ÖºY¹–2‹’ê·9 ›@:2D‚-g‚8?í2EL ð t,T.O"؈õ'‘1«îIlb·ÃÇØ%ç–·"¸7AHÐ&&˜åß ZüK`lÚ„k¸}ž£.Bë%°´>¬Ûw½òåeòh\"«Ï#$­¬« „/p?çþÎÿeÝg(}™a@*Ãìßóé M\¬Úi"‘µ†Ï«ód2á|0 ¼­Z¢hTFØÝ©ð¼µß%Pð¹ˆÓÞÒEŠ%XC{\zvH lFåCùpo˜‹6Gkõ©¥a4Á±Ž¾á®/
+ß‘
+ ßbGð’ÚoÒLèÔð…¿ú”šu‘f€’ùo.ØÉS!±Ë°÷ž±Á'уðÈÛÆ~ “<è
+ ©‡Á²¥;*9#€5†ŠÜ¿÷Ö½r±ÒíÓW0^€Çuß·Ú¦å†o¨rü £y(bë4S¶Ûáò›l‘½÷ZŠh]rœX1z ¥ÓÓ/m/Òvè¥Ü*ÀlÑ ª~Ñ7Í°Ðã %‡’”7é}_­ƒHÆödU 'u 4U«îëìMu"8ËVµ™ƒ¤¡ÁÙ6+2<¥.~+€.×ñª"ÀµÝm Þw€úºµê/ ‰Pã9zg‘} i¿¯|`|³€QjE‚Þˆ¼†Jºþú*lÕ(Ùó…o
+ æô©+Ë›ìËŠ¢Úñ*£¸~p|'_bPv`v¢?_{2Péç ¡ÞL‡ÆÎdUôý£#{Ü¿¾—:V».àúVƒÞ4ÀxHPYÙ]ÂÃ
+ܧkí«Àm ð¼
+D4¬•ˆ ÒF£ÜE·Eq†û¢¬äE€é­™ÃEæL¸µ3µ¶q&íàÛªÄ
+óºý9©8•ö¨Cñfs&q$XJ{û; nfë¾­†€]ƒ
+Š¦¡i"€uØWb̬!Qßé3©TM‹gøæjÀIlƒëXÑéTñÐËUíOq/0!­}Óf›Ícᨸ€55{ÉE-à“gZ·Kš»vŠÄµm@óÑô¦Í–çig:“ЬÿÖ—¡4ßæêb Ö´ EÅ SI0nkV‰U÷$hŒG·WÌ_ª® ³ÜG´D^íÐÎC
+Êxú£2Šôp:&ÏrfßsÑ·5òSÌ7€ìï
+–‘°íuAzÚ:Áøu‡f"ÜdYuÁ"·ùä±Z´3û9é@+xêCC7ó›=°œhædÿpý6
+æ•ZNø…ú>øÊ«¦
+_w¥ W´w~ÊcF°Ñ ‚ãÒAìõ¬A‘üƒç`‹àr®ŠG¶9B¬¸8ÙØÈ šoªÕÑ©0.
+¯E)JSk纓Q:Q „¬_šÍ[JLoØ]&þkÔEkþš‡fËaQ5–!
+kì/…YkºpD?ºA{ (2bìªþ fgV)µ€ú 0-8Zt?Öv=€çÊš(P§Øb³£š=@ëSõ
+/¡K2ð¯R‘1ÀW@W`gy)Ò? 1^^óx"ÆOµ¸à‡³T¨êzóá1äÖÇÖ—Ä@_Š -ÜË–½¦Øȱ9$K‚³µ Îçá‘V8ÎÑ:ùíE×¾~ÃEDD{îâÏ€I«€7K X¤ØkuˆÚüæ—Üf«´ê5¼1›jF €öD6m-0Ã|°ÏöαÞ­R‚
+pùáü;åÀ[oÔà1Ë1yÒÿAVs+â(©÷·Àœ8l¹|Zë|:&Kp’‘~4s˦;.ðPçÂò9Ùc”Øã{º‰kr™|#àîµ´ó[¿¯ÒWÐàåÅ•w¹Ö%›t®ñkjºé[¶æÃIuù¿¶Šü
+–ÿáceø,«‹³’e°À[2]Aàç?3ëfŸG[¸†ÞU~ßg1e:7cK}0öìb
+²mDG1‡›2LeátIhÛ|íÍ
+¤+AÔ]/X±Ñë^é šg»#k¼
+‹Á†#©Ã
+“p´dã¸ÊØZ“FêÊj¡âCek«·W„­JuæuÐdY$¢çæ*Õ<±µä Öb6£$€M›bàãTAoQøwOðXËùLãvc=«¬FÂ`ûŸBkˆ“Všz€ì^ßçê—=6C,®!Ƀå‰fèBÀÎõ(”„Áà|'m“ߊÌÈEç Ò})z@€väõ¢ÑëWÿ~Uè®þ¨òø€øŸþë_‚YûÍk¸ÄºøY°žÖï‘yX;º™IêP^+l²]¶nóhÍôÜ»9o*f†Ú•"Ap§\튪ʀMÉå¸CðÝ26(teõ\¹ôªM[¦`jl–åÊË¡»õ‚£(?úgèWÆ ìÖGîrûµ
+\EïÑ7Ív"ÞwìJUm¥*V¦\®K™°¥YñP8”÷hC¡,_ºSÝä‹uÉî×˹vw£ª0‰üÝã_Ùíbñ!¯íc7-ÍMAÔÁÖ™¬j§‘Qä¶y 7!™âíWâ¯Éíºõ*®³÷QÓ5ŠóÑP¦j²ÙM#Om²Äµé<
+a.d5·ò”¿¤HÇßÒèi_aNÓf¹+`+»a{ÀÐÀ1’§è:ã“ÓAκ Bu¿M)«ÈŠ¬Y—B»"g &‘D”(rèYKâqßxtÍæœ$7%x”ds{ôFŸé±Œ&µûdÛÂÁL˜hr“ÐÜÏYË¢X2iàÖ>$<Å‚ç8öÉ;Çèr7Ú‚¥ÞÛ¼— .KdòZ¬w2ÖÖÎÀPI/veÝöã²æ[íþùíwŸ>ÿðþíç÷¿||óéË〾úó×oüüéýÇÿ<¾úñ§7¿¾ûþ¿ï>þëoo>ÿô—¾~|ãCþú‡üý˯ï8èOø *GÙáøþ[)P¯¯xÍ2r1}“ÁâqÑM”ím¦»#zÆTnô«§?B]6´Ûk=HÝ o:Í? CùÏkž ¼=Ç0žë·ýy:cµIT`=lHîƒø°!LR~ë¥lþ°d¤’I¿±]…·x¶–ÓG~ß ®|ÉýTÅFrPœåmÑì.:Ke; <
+¹KŠ®õ“=OûJ^”>Å9Ú­­YôG"ëLS¼gÆ“-ç¦f9éÉýS¨C;3UÓÉŠQ-e{_3áQ”DOKrÊ(I„s…º:Ó5,…Œ€[ª®è“šÕn(Th´-™¿ÒŒxë]mèsï´bWug wΘ÷—ô¶ ä…í§í€žŠÐzÉÙº¶J»óüKª¶éä• ùGÅyý9_G…Œu9LÈóªC*–M
+H‰¬WÛŽ]Gý‚ù‡óÉFpÒ—ªênxr&…‹@„%„"36‰qìD–£ÈϪ®U},LÞå‘]³wïîêu+é—_Œ5êuŠ´ËlM¯}Ú¸<ÜyY®Eæôr¿Ž>ãÙq]e¶(J¯æÅY®6—²Ø˜õZË’ÊQÔ6z. Æâ’šOj¯\À®u(w`¢ºŸ]שµr·¥íUW¿b¿æE»–^{,°ì*VV”Åp/®k·ÞcÕ «ÎR°-5.°ÐŽËý—N6Píå*µ4¾?Ç®µ«ZÝ›ZhK]þ¡.WC³Î÷WWßkW´pí.½ší¶ôÃJÝE¹¶9ýû}^Ké üë®\ʵ~˜ø¿ªŒË«»X¢ù…ài´È°Äë»X¤÷5£lÚ:[Sãã:ÚŒí¶<­—-?í.cÿÜÙ¶ïfK”;3ì$ŠÚÛÈí¢±±@¹b‘åÚ{ô­›ìAÕÑXëÍZÛªñ:®VGá±Z[qߧoÜkím¯9±íZ¹Óa¸ùX` ùuE™õÚy¿ ‘,Vmñ%œ¦÷«N–MJ€Óð­l¶Vî
+}Ÿ…OnŒÄú£ tðÙ2UòYY칸—X@¯Rn;˜±[¹ŽQã4|Èε®|-™Eⱞdñâ0Ö ·Ä[|,^íà~ë¼Ò5òý¢•Å¦ºv±a;]¢(Ø$hqŽ]¶iÊgÁ¨Ýý
+OšÁ€ÞS(5 •È臿#É´Ú c®6êc·˜&Žr
+'Þj .ŒJ¿O––`›Ñú
+˜‘sF°Ç³³–&)­R'=¼0QZÃñö%Ï‘ï&Êqñøʉ827×™Iµ…H"þš<·ÔPnÝ®}ÏkSkši(¶éÅVyÌÕÎûm‰â@Óo…ÆŒ•Ž¬‡Žü?©ã,ûä«OŸ¼}÷ùˇw/¿{óìíûË/QzôäñåÓ/ß½}ùæëË£/¿yöý‹Ï¾}ñæùŸž½ûæ‹Ï_~îüî'ùËûï_ÄC¿Â_ò:ÿabus«BdîY`óNØpsQÞšEhÚŒâA‚0³r cCjf
+ž0¦…<’%‰æž±å9Ë¢Âi[<ö½ˆfÜã6aî¸qhxÔ<sžLÍèe%“qÍ~¹›Omž)GSK€Ô«c[³*­óŒ.îÅ+‰§vø09åà%[•¾³Zeh­1e9Ì-§Í¦vh³¼Á8ÙÁ÷í{Z–£[)¹ÏÚõPB:7UfÆóÁ%±#é™ù3
+AKaf˜à3_€ÔézÅàç¡cÐ ,.x¯º§Ø{FîÜ*$$ùDƒw’uÆxa¼Û|¾yþè;u MCj:Qس{Ž– ’p‰Î2ÓËgšæd<ô±tPZ%þ]ÞhCÖ&¥ ŽCÏmëÞÃÕ¢°káëñïf¹Í'Ì}ÞWÜÒþ8@j.¾¯éW4×æ“•¬$ê¢;èÜy6.‰«YbÌs”í¦¹·¶Lçlžszr´\wŸPó¼ò1`±ˆC^'ÒSÐQRÉ›é¨jáƒhdZeÑ°e¸¢ŽkÓ*GHr¤å\êÑRc´ZnÑ•i)“áªÁµM[s4?Ð4’ŒõÛZÁpøQ‰Ž:—, ¯mÖ¥×gÞÇå÷˜éqK’#]I‘Ê´Ø×2ŽÆ­žd`ªö©–À…IÖµ²ÈÊ‹zÆyÉ¥dP 4Þ
+ÚÖÈ:>½†Æ¢(€ÿa˜4eÚïW¥‘fBŒˆrsEXªg´b
+ó‘|̼ '.qƒ®òSxÖºr¶¨0Ô´Ý·(ý $Ôp“Ú•j†¹¥üºà…tx’!wy«pÕA¢ÑѼYÇ%†k[LL œÄ |8“Iå—Ááì¿èwã)´ÏyŠÓ½â–0ûI
+*ŸÃz›ŸúÌLÞyr§D@DzÍ\s*[´z|GöHŒ€´ìæ&4ç7ÍQ4)íxÕ›çᠵчsRA seŒš‡åŒÀžô­óÙ-·!áEË¢V4%è4òzYt¿n;pIY=i2G‚Ô§‹{^ÜÔ!¹ÑòQ‘tmÓSì%O€gÇá9A9¤ñÙ}[AÈÙR½Jͱjz?
+y¦èwƹ•é#KÍiq€ì÷ 7óàM¶>xíÌp“C¨J­ìK×”j0¥r2¬GP\ ö.ZêGü»OªâÏ«Ü CT Äô
+q•9>(S¢çÞ¦Êæ{?ZúûÍÛ™5’L±g²IE)gÈÊô¾éY$9žQÔŽ£¹òÔyü|åèJáªåˆÄ¤%õÔÎ>ùá¹>êÁO!÷ÿÇ1䯸Šz‰¿Oßï{yúüÎ._žþ ¿ýäɵ|õë7Ï¿|ÿúŸß}Ëÿöâë—oXyô›ÏÞ>Æ›Oð÷éw?àÏÏ~À¿ÿÈ¥K.ý[üãß(ýx‘Ë.ÿG¹<÷WþŒ_ï˜V?Òp_`öƹÇo¡Ìè‹ÚÚð“zr–õP×]mßl3K«ByÃú÷,¯}aÂ4ã5HcEcXBQûeQlÖ)C(²£¢ ¶8£ µ.au6söê @éT”Ûدº+#øûç&Žrì™é™ž™äŠ)²´¹Ib)wFH^ˆVDŠß>ÕÝÕßÁá cßåbY(曟þ©®ª»<ZX[ëG‰uMŸlƒj¼Q2Ç‚ â« ÉWHr=ÈQûaRFŒ¢X™Â<;;°»³PA+„>bu¡¡Ý¸âB?B…¹àÅðÈ>g(¿ØD@V43Nè;È‘£ÀZ4”ƒspqƒÑ™E þr`ÇÃ
+<YÊÒB2ÜS(®›K÷aÿæ*B£+u¤#“€€]‹…£ØÜ8V6i§ö`–‹M¼šè¦<ØÉE&Ivƒ[Êð^âa[Áã• H”á¶öéÉf6><àÛà$mtgi€½f ( ñý¢†–è¨'¼€”E–öf¾Ü«žà Á(dø§)ö9ý—FÁå® 0Ä_® 柡…ãR”v
+ª==a\Pò~Yˆ™Îh‹m¥ñÇ
+¥yeD°G‚¢¾y§ë xÄ®3ŠŒ»./÷ŽŒ»¾÷óG _ëãY;à>‘ù;Â>€Çf¼¡ÞÚ"ˆöÍxïæÅÝñ œÇ®5ˆÎÝs)”žÝk”à-&føDˆáy©­jÕÄã±A õaú˜=¿M•¬
+‡Ø¡•$а!€D1ì¢vý
+D¼ãý˜’¶¶dT´
+vÀÈ5ŸÖ.N¢JvCYh”9œKÎÃw­¯Û*\jÉ<»=Z1§­5;ýÍ!©
+eE]k§Å§°Øwp“¹Nwg€ú†^ôÒÃãÐÀ³ºõƒ\ï¶Íþyôú1úgKn÷ˆ[¡×&Ü…Û’‘ÀŽûÐÓŒkà±£ˆr_!¿ïC×Ú¬úôýÔ*ä\¤µ ¸…‘
+‰ûœƒÙ6ZáYØ@™Ä¸
+’!N ±•QWç´b
+›aÚæ )z¡,üh8OxOÆÏŠÁj&5¤p7‘‚à¿H—»ÝNõâŠáìn²øQ‚1d]}ú/ð[Å­’ÞPõKèMJ”&¸§›"·ÿ4zn¸ZņÀ´b™&ÄØ­-«T|ˆú©*W¿DÌpè°Äi_7hnpÌžyûygBc DŽâp•ýbû±‰P›u(|ò<g<x ’¯&¯-!…MZ7ã5‘ ;¢äµ¶:y­fÇÍðÎkP„› VN›•Ä©›ÌVBx³Il€äcì%µqƒ&)©¬šÜ6vhÖ~øÌ"ð[R›µºky¼êƒ1b¥úŸP€õî>ÏÔé¤MÛéi-³imxÙ¬3fHikŒÊ¶Ý3]`ÿÎÄjË9Ð}Î8ª”_pYÙªK” ôŒˆ¸ÁxXÐ.F—°^Š¦pÐå}_d8¸ˆrªb½ß¿„âÎÁ0>¨xÿ†5ÄoØ=ïµ®ä^õÑU´P'Æpgs¨K1¥Àæ,ÜüãOgûûÕ¶#ÕuD¿ ÿá¼ ”Œ÷ý’<Á8Rl·åbɲ Æ0ƒ…!þ>«nûìÓÓ“Øî\^"{è™ÕçìKÕZ«ªŽ‰q«vìé(þp›ß·ç£_¹gï R-9gáýßáŽ:j/7a¨@…‚5‡òÝ•ï(óê|õ.IOQJ:µQú•ÐMs oa%
+`-­Œ 3†Î™Á^QöêXéÔa€­DV’p¡u@EÒpc/I—}mL±Ûò[GCð/,cY‡!øÓé¿d_‘Æ9ØŠ}¢,ÊŸ}s:íêÑãæÌ Ú=^
+µ¬žÂá#÷g¿oÇG¿fÇæТÚ%Q —áÿ½uý†%¿ßõåþƒåéøíÞï¼{ö—ë—O>_½xÿNÿ~ôêõåµ"÷ÿúüæâÍåõëåÉÍû·¯àšñóôÓîãŽ|êá/jVwN³þÍÎrç>ã¯ñËÏðQDêÓ’–o—rËKZå±hŽ\(ŠË\ =vˆø»Ý“ßzæýåë77/>¾{qp\:ðwãœoŸÓ=d¥yŒÊUÈ¢Õ«-Š9ÀÉ`„A/²¯C‰ Ú%bšŠx±»ëѱ$Ïbe‹a*#‡¿Ø ì4›Ñ¢’.·›+x®–¢:7x¯ÃbávtZ|‚½cÚiÆÖ3­èzúuÅõ–Óæs@Ž=º.¹ÆxÅ4›Ã]ŽÛÍãYÍÚE®›+x°ùúèº$?—òC[ãb=ÜÜÓ<·ÝÛ¡;rå`oÏwÛ)¼ßd]aMûØh…æ­èzöuÁõŽÓÞs8Ž>:–\#|Kd†^ŒAþ{»“~Þy.¨ZW‡(Ê“I±Iˆ0¨¥1´È#Bbæ4£{E1þ®Ð¬ŸŸ÷šA>–iã
+›aúÒÓv8Tj<ÁL{ô ;—¬›ÎzÎ~ºÅ~ O7F¦úhê-6
+ï†mÈ 9¯ q–œ‚ɮʦ‰”ÖOÓ¾ßÂD‘õ`¹ñÝ&2nÅó“F<3îJp„'+(M /TÝE7)ÓÅð- ò>†¹¸š@1cä@@Bœ/’Ýp–NØ…9xg~L‚KQHƒýcrm9ªª4M%7š:µdtôR7CëÚ­µb“¤G’rÎ:vN=Æ¥Û椆ÉIAðÆŸÅõcãò7­‰HÅ8o~üT{d?âGãÞ³;­ãÞ3‰K÷Ž³°güv¥_`,õLp*¦ ËFZt×Mš± sP#vÉ
+Hy×¹³‹ž ¯…šë(DÄç±2vM”<LE9Å>&ÜUù"Ñ™Eé.;’N‚RÍ[0R
+{¸¬€FK¡ÀúC1oä>›ž³X¥æM Už0gݾ@ò|Vs¤tS­¤6[‹¼Ô~×{8Hñài°Ñ%î¬
+nå"¨u‰í›ÁÙÓXÍ@#Í£$O!N"–#éøg´yøo¥Í÷øéËýËÓ¤íÄ?øyúi÷qt Ëwww¡T'¥^>ýŒ?¾Æ/?ú´¤åÛåÇŸÜò’V{¼»£àœZnŽ›KÍ©…æŽ2sr‘¹£Äœ\`Ž–—S‹ËÉ¥åŽÂrrY9­¨œ\RN.(ÿrÒ¦Ù³‰ê©”´
+¡éìI p]ˆä}Q5ƒ·×R88o oUŸ-UfO€1sg+RWÒPN,1,ÖMº‰<ñÄ~–|ë¬EAŒ´c\ظA[„•èT”µ˜FêÀø¨™ù‡ôÁÅ(¬-¡šKô蔵`BP;q=j­Û’ëaÀ)šõ@e8K¸ zïÇ>%¡wÏÑëa"uÉÐ\TzÁ~ðOùÕðlóB¥Ð¤Šƒt˜@•µ¥Èl†1¤;Ÿ D\eGW´êà ~–¤`ê*üvæ³-
+ëFbâ`•½P™5Ç DáøªÏ„‡ôç…f¢¨—Íy‡%ô1›Fë+"¥Õl#±‚Å yŽMš½cQ±é횬rDMÝ%HH‰.™z§®×©½+uâš÷Ym×û ^Ú‰^
+¶.ü9åaÅàb38Úû±š(|1+×–ÁÜÆûY Ø×µ ð¢ÀZb5ÏsS€ ecd¥Ö® Ù‡nSÈ•j œ+½Mà(Q¾V…«ãý܃‚MDuÛBÎánd:*™‚ÓI8âj:ÚjEÊ ni¦ƒB—Î%iÍ-+Øz(
+¶Ä„C¡ƒv5æg­á=%ó)ÉÁï³YŽ É@\sXŽ«lO‰¬Ý‹bQG:‡ö©6Ø7;
+˜W°®ÌkRôÁà\ÕlBp
+ú껚ËYm¥êh ÉíRñÃmªº’r›ÒšÜ ×`|ÕJ± t³†‡ß´Ñ¦@gBr“a>ª‹ç*©"°0%(-aø Z½ep‡&$Ó¥Èlƒ#&Ç6
+E²ê£¥Š‹Uš¼‚=™•Ì“’Žôê$ß–Íœr1Mé‘€ádžáÀfš¡Ê@8J ê‚JëÛî1ü&‹ßÐ,; ¿É&×H¥4ºRÊäw÷`»k>‹o{0UÁÈÔHT+\ùÛå’­· Ãà-IâCÒ¸Ké8ûŸ$Aù&é 9§¸¶›"?7p3Òò}¼uí¬”@qæt@ôŸ Û4 ÕºdË8ƒ]°Y‡óJˆh*+8´óÈ5k!G‹QÓvIzÝ1éM,l šœÜ—tT’«ÇƒÛã<Ñ[´õ=À
+1hÙQÈÆ[áqÜûÓLhŒïšUoî ƒn– ¾A»¼VÑVFƌчƒÚ4ÜY5à!/®i„¹
+'«3DÝgÌUxÆïãÿ>““ߊ‘<±D ;Ì ø
+>ÿòUÑ Þ
+nJ2FD$Â0?î¸öמµÚÛX= - 7 ÔœàIF>nh…±±j—2ŒéçxêS}”{íû _PÜAºé`’‚}6Ý䎕áBá¿ó¸ã¹ÝcÑ­ÑvOe×FÌ}Qé‚?úºt²+CµŽFsŽÊZÖ†ž6EñÇœU+³†Nl¾ôòŽLKâœæ¼=ªãs«×”|w4`VñǼ=WW¦xäA,æoᩧÍö[òÒÐÑbþŬÚäàO‹Ä˜øû/ÊWB•Óf
+cΊ%ÚiºNù0P?à`#µê·£Z<D9Hþ­5;ïö4u8í –O¶…u)À³Ä³ÛžðzÕW…m•æ¤
+óÎè·÷`üõ‡+”Ž»ó|¼é W{|q³åñ*nwâF¹câ]ËÅâ¯TˆEoaÝážþåç[.žhz©vm€ À·5mt6,~°Fí‰ígÉ´d§¬PyKLGùP³±r®}§Pq-ƒ5[È_û¡f³¦^E©IGÉ­–ì@‰Ǥ¨à釬/[¹søgqÜO®èËfieó¤ñN¬ÀÒþ^­úG}ÁÈêŸ8ÒFÍžN‹ƒwróæ
+T9-©T`AÂÏâÐ@‡¼ŸGVA
+
+kGÞÌyZ´õñFÇャb•KܼE·E`%ïžO=‡S gídËÜ‹ó‹N¾-VO‰QØ•–oá:¬6k è{† ¿
+ï9Çn¨¥†&»HvËvÃdËzàY§¾ò½ ­6€5…Íl˜)“#DñýàV›)öu8ìx¶‡Ô Í3ºBËäY÷¼@â$œu_ ÕºC{"'MŠðolzÑv;)q’Ã\âù²s©|«ó³BÜÝuñ€2,¼fÄÛêjF´À3ºbn/ç6*ãçÔÜS~ àY—†' P€¹s76¶?ìÔi…SÔY¹Aƒ Ö~GüÐØìϯD 0BÏê僎² K[‰—‡ àíøM¿4vNÓmäËgàä FW.–ÿúüìJBî_‚Æ2‚hÒïl®Ó¥Ï/dF«@ÝY¼¶ð„¦ØKÛ\0èÇ™¾Ûïȵ‘&H;Z ©^‹úÕ. è¬,?Z®CthøBFŽäµX;‹€’Ù$«µï~{s‰D4ø[€Î,ÀE0¶KÀÕŽ¦ŽÕšp/Æ4× Ç]»E™‡WÚæX_¸Go½£ÇùK¬ßß
+•tÒ¥w! ±²ì.y¥¸þ·t>^È‚W&r¦ï£â– ¢å{-p6‰ãq`¬Ý·+Àex›UÄ:~}ï§=õªs.ð@›Ä‘]Œ|9iu
+xåOѧýE"ÎJâÄ? Ûù8`:\Ð$s°NdÎ,Ÿì- A[¹å“³K:çÁÁŒ*è «,ËÓ®ž…;š©vº 3Û¡t¼”¨ÞH¡¸üÀè’©b!ŠÖ¤^Ö˼,‚€/A§õ·­°ášãðg2÷ç”ωre©QÔjéc1°a]ų‰"ïÛ¾R}Öc[66õÿÙ $ìi‡ÔA”û¨3Òa+Æé¤ÎJþ Huf\wr£aUQÇ“ÜQƵ‘00Er0F¥Coã=
+8ø@,ÚuÕØ(wÿžMé€Ķå`h‹Þ8ëýÙ;¹³V“³òÕ¨Gø#ßc¶o¶Ñ³%¿ä×B_x¦íMð`Êw*ât½Q<Wd!ÇOã ¬
+=XõuÂü—Cs"¿¦iÆLæœmÆô0,·²ßô˜¥}jîMn¾¯â™Á²ý)¶QΡ±PXQ±ÎñèV±s:Øú½Ÿ–1ÀsÓ.„³9“£¸n?ÿÈÚ=]ÄØÂ8 <Xc{ãR7ïÑ”ˆhòí÷7Ú.‹Øï2ÚÈÚìïYE´ ì”³
+ASÛÎó¬h oñÙ
+Ñîoœ´ÐZg¹}§¡ð£Y©F(I`§B9Ý wËa¬[2ÚÀRJ/‰Õ´”ÈaÛÐnR
+Tí3à2Iœ~IBEÞ¡¯×ýùÆçaòŒb¢z`]±Ð–U{ô¦ýh Äš»éa“cãb™.|Ç"Æ;àᔳ/ܹxÇ}ª{ïÄ¢´ ‰ŒÔµ>‹ažÇ(Éž‡›Ô
+ç`ªõÎää£3¿“ZæÊh¬mýð±8@/\*¬SÈ: ïEÖÑIÀ äÔ–ÛAFëZÚ‚1LƒÑÔb5æØ×Ê{©1nçÞàRz?¦PXÑ°ÐéBcnëù̘GÜË|Þ[cL 5@¼¥rµMЫã¶Ñ„¯
+¹ò#y¢_kŽVßMyk˜‹j4FÕes6\PõÔÌ-ñPœÄÞ‰Êg €œ-趢™=D.™,¯ëËÞ@güB_ ÌwrF6z`‹tbŸ›’ ÚW€å’sv 29'+çÈ\xšœƒ6ö=¨¥ˆbÅo·Ö ®¶¹ újøhGƒŽOÔ ÏMiÆ8Ç7e 1ó\‰§¹r&Up›KÝzÚÜÕÔø¤://g¶ª'8sn|²¬9ÃÕ2ã Ã4µL§Q´dB$bçyRIðæëJê yKž%šzã—•2³Ú*ѯ<½žW&Âõæ´X[ŠÞ,~‘84èx[õÄlq¬t¶,Æ?m4ÊñÓ(IþÐ6ªzún4[ öxX›ý׌BÉz»T32N¡{Ãk¦É8È,I_¿t´`N\ÇL?h¨Dš—g>+âi5Ç‚Á²QNs·
+íÍ‹1’YXï›qÞH–ªEa *9ÃƼ=€ÒÌxpöEóìéu|,ÔçšÊò:¨ g±.«2¨Rµ8Í ²†Ñ®ˆUÚ0Ë@S£+Eá4ÌÉ¥w£¡ž
+õ=¡WçªMãÅ4‘2mªÑžµÖ\Ÿ”WaŸžØªmæK¼RáÇßcÉ9M9'çÈl/ΡQ1%Æ94
+€‹'#¢ÎXõÕ¦
+´yO‰\ã
+L“3ÑOÉqßâçZH1×Â=›ÏZþ KfWèí`ñç`îÇ™o°µ<Wæ´h'Ù¹"ÛµêmÒ8$›á‡ëøÃÅûA== êñÂ@¾£zpÑrÎt³ºO^Aaž8»_6 ½ví°]Š!‚’e\³qjQ¹Ÿ$ê`£W(¢H0¡Xó~Ä64I¬sž ŒBŒyšƒóRvð)»Täš³+¾Ò§ 8/b·v;{# pö0³úQˆVKðSjW†ÆäÃqaÓò¸»+¸Ä½=úáÉŇÏÞ^~|{wûæÃçÃo_<><yùñÃÛÛŸç/~óþúé»ëÛ«ïÞ|üùųLJßÈ’?>¸äÏŸß_ë¢ßáûìÕ™;HÈ÷ëÏø?®ÎÊáüñáõ_ÏÎ/®î~¼>Ès‡±Ã/ÇJ´ÑëïÎ!€DRrúgÔÆ/õÙ×ãkÂüJÑͯ³Gîá¯üéxίùt˜{‡ã•OŸ·íŸÅž´Ïöiÿ²n ÉáOwc2ðÿ¯ÿBupK]æ3ÐW«£ÒÓPÖѨ Ñ“á®[÷Æè'ØÔ©Âs93%“cƒº‚ɈtÜ Ââ7èâÜÀ{S‹~üÅø|‰mLOUÄaë+Ư6r|—81Ì6àµD&¦®´Ùº=Ná‚î@Îøñ
+ÎF?> Û“Í™Äî×ïá>ÿGqH1~ÓÌOŸ^\^~ºùþîãY»édÝî42œÒ ad[:˜ñÎZ°ž¼±oÑÌÐ<Û0}׆ž·¨éCÉÀ̤9+'Ë5ÔÆô ÷½6ˆµŽƒ[y’•k%¸‹R8šÖàŠõÇ°.”tsãøf/y§Îè~ .0ª¶(﫳ÎAÙþöèâ…w?üþöêåç›ïÞñï§×?½½%rþí›w×_>¿¾ÁÇ2dø~ýëÙ§ñߟ MÏtêàèD•<gî_d&gÌôüòO@¿ÒáÛÃßÿáW²Õ÷¬ÏÈzq¢zóq
+®Å«¡Û€QÁýXåµ6ª Ró—ƒÕ}êõÔZdؤ‚q@šÙQAÝ`Á~­­§Þë^ ¼ywxòâöãá\.ëpywóþîÓíÕáŠÃÍÝÕõñX5˜:x(U £¸ /Õyô$ùH×f¨³¬UÒá9êƒt
+*ˬ  F‡!LÐ);ô[ÈÇ‹áǶ‰PÛÒ{ßÀ¼®`r€ž\¸† [œNåÞ –l?Ý™µ dÂ2ð”Ô`r¹…5GÒê–XÈ~KM^†³br‹ 2Û1Ì&oNÀ4)>c¬¾7¶$âÌÁØ°}¨8.¡€ÑÕ"R+±ÌÀ®jÈ^ÁK @qk ز }Ò‡4!L/Þø5zA
+D¯[í‚TL»v†¨ÉòN=Î;ÆþîŠÀjÖ9¾ÄžËÕDfÖ­ëí°_6žOU9ð" µÎ44ììX6)HW°×o…3c†(¢ze§ä¤VÁž¿ë™m©˜$^}’;<`axy˜°§d v¶…>1&—,§TÆRL3†æZß0®jظS¶þ”Wzã’§%Z·Ñvÿ~UÖI±h
+²EÕñ™Žv†½Ù½-×½¦Ê’ê3emQ‚½c,Ðx+wØ—¼ÈBÔ©¢C0Ù,¹ørPkDSW€á,º©ï}0Û ‚¢£%§£T À
+iüXkxg(vy÷x ýœú«|(–ËJa/½áìlG˃heѼoƒ¿ ô÷C]=^^üp0ËSü|w --8x‹ðaùú`—¯·|h¢ºf+Ú¨´·›™:Kš'`ˆ{®\c™´”—¹}õüáæÇw×òýÉÕOoo…<úöâ-ÖüôâÇÇXÆ~^|8ü‚?fù~ÚŸc\~ÆgËß–øò>ü èÖo–üÓ,¯èÙïºþA;a=³‹¼ÜL‡ÙÆÀŽ B!ù.(Œ"óríE+5sGÅù‘¾˜²´ÎʽCMpʼn®*5®³Nñ90ŽP 2¥˜3«5ôÃ*:nacOüJR>fÖ¡±à°¾(ÆàB»b€(^¾Á€ûâíì
+qÐadˆFÑâ…VK7{¶ Üøe¸¥k8_ ¿,’Ðt…þh¬ïF»Í®ÛÆàëBâ$Ï3Fù2tQõ²èR¶!NÇ ÃË9T@Jn,nHòR?Ю«ÀÒŽÌÆ(Ï[\#ƺZç¢Ñ4ï|0¢]:JæŒÑ
+d'•Ü±‚¶t¾tp"[¢A«0,2¥fÒj6l)¥"€!©DkçE)K}ˆ“m?/@ÜúºƒÆ;¼俵[Ûˆ«ÜïrONŒiÅ8§àª±ƒw\¤’åAM'qéUÐH¬“
+U½‘3¤ž#%¦9@ÔÆÊ%&¡®ÅCšy}ôêMì@㶈¼Ï]¹™ÐŠ|‡¼„ÞtÜJ2koÉ—^›LO­ÈcFH4Ïðã™;™*ó Dti“GK9T)rï1jVÔ¶hC»çwNûöYf’Q®I‘>ÈPg’‹Œ}]YË벜哸
+þèpëX90JhÞØ"cM:Ÿ=uØ¢ðõÉWg¹…†*
+n l׋ ¼’Ö5q£ŒôU¦eϦ¨•¾ÄùÊaXseÍNè¥Ôú_ÇS´4ìa™ŸW´’»ž·ÕñöU(`vÞk·¼œ«Æª'
+_À¦•›äôÞÚu_ݯàÙënÖ¬Y7~YßNûÆywËu¼åïbZç&˜)Ì }ØWQ#µašpY§;ÓþT3"5ÓËfíS!RÄÚèNOT_œšÓί?}~|u·<»¸½¿º»ÝÌ2E¢ú¢m#¬ÿ@ñ´9×}âL‰
+Ù‹µ:j ðwÓµm[°¸˜ E(ØܮŧXh8kSVXë®tÚ‘õÅ/ÏÆëòi¬Wqˆýáå/øY·i‹ mc?ƒRžát›– tô&aå3‡éK§¦åš.Æ Z/£ýèï—ü0 èÜW†èpÄZ¤{T¬¿½9Pœ2Š—Éi¢ŒäÔioïßåíc™ÊpÝò¸Û;9 ˆ}yvwÿôíåýÛw·wËŸ€=y¼|ùüþîííOË£çoWO®¯n_}{qÿæ¯O/$“¯?jòýÃû+1:{öì¤Ù7÷—o¾÷äêõ»;±ý3~ð×.gïÕ.TèÆâžXßú%FS{7»_@‘Z¹Uh_¦P u {¦ ºÞr;”HB¿z_Ël]ý*]†_e=Ö_ ÈÙšºL~Ѭãn<VÑ¡¸kVÖcwÊï>¿u¢gÿŸ={â3(J×Ë^Æ ®ªKSMR—õªÑ‰+ýú3¯óg\æϼʿ·‹<N5QAíd9õÑÙ©QÐXQá7²{\§<e„œpN%ÍúºI3–++ÆM5&õ3g§òšäÔôþû¹´~m‹ÓÄ*'Rwª¡Z€Æj±Ã«ÚÖ>t€#d!'jÏ©Z3žU  mð’æäÅ45Ö1ËQŸšNcþk¨-«tLû+"´W¯ˆÆM»L+裆œõ\b»m1â·XíT1õ~EÕR‡Kµ©}
+Ú«†j«^Õ¾ö1˜ƒ–"•ülÑQsYz_E“¨y$ªS”1åV©2–Á"AAºëÎKÐVLÛ«†ƒÖºtô¦ôD“÷wÈ1C¦bµ³m­¨4ëIˆÓ§(<;¼ªmí#0Ç,TÊ44Ë b–JåvRê“)²8˜¤0+×Zp@=u%NÌ£^†vF%ÍnÚ—ˆB‘ûº¾½3Þ¯=:ʌɲÀ²­rª ~ÿÀj©Ã«ÚÓnûs¼<MRÇ@ナW4{‰q¨Ž0Oo¼Â¢
+Bm•‹”÷ê±d[R/ûÓl;ãáÕýÊ{ÕôÆvÛÐ_0ÿa6’, I”DiéL•v÷eÛ$ݤ Òf‘ßCòðÎÛÙv€÷ì9Öå¥øqxÕa~ð Á¸3Ö>mûñ,†\¥=Z=gîðÉÛ»ÕÓ½ÞÇà1j!`ŦØ)jÕ‡-¨ÒÉ £¶ëî'8†]ßÕýÀ£\¸ƒØÜ´¥=Á¨|9¢F«ˆÄÒrTZx X4w? WiûÑê <{p‡OÞÞ­žîõ>µêûC‡¨µƒÍÞ/«ŸßÀ˜*µyFÔÆJhÊõ$»d¡ õ“ Æ•ñEöz< .UôÑê ¤9c™Rë«àõÚÕNŒ¬ÞžÎÞïu²ú>½·=ÿö¶çý÷çßOkÛ÷—rµ¥Ãþ}ú_ðã§Ë¼~ùÕõÓ?._>ÿôë?¾Ú+®nå?_ùIðð§¿_¾è„^â‚\þ‹~{óñgþ„>+ÇçòEùë”ÊÇøŽŸãéÃbo'¿ùn¿J>y¼ûùíe¿þʹ^¿Å/CYWãŸÒ¾¤A"ýÂp•W^ŸîÈ1º²’[G}Z£ûÔÜÇÔKÖ1Ž.–ëkªš1}{ê@ûõÅ·Ž‰±[†@éÞÚ&%ª—©`”ö»
+>D° Ûõž]Zy×jµêw]xPb›³j¬È¢Q[£ÌÖš‹_Ùλx]5½n >M¼%À]qòÆsÝ·ÄÃêÔ‰ÝÓ°ÛÖp £ÃJ(Þ*(•êÛ‚^g~ÕKÜEÖ7o7½Ä ­êD¥œY4ºÑó·ÜÏ@¨ë®ÂÍ4´¿Í]#¸7 AkÛ”ã»/®mHÊ ÝñTw´""È¿Û­¨7ßñÐi³4÷ ˜ÌNpù¬ƒ1è`_ê «@A=;šrÙ^ÞîìÀEr °Ö©ô€›«UxÈ 8‹>œ%Œ Šoû0«cP8ûoæ*³'Ö-c$8iQ:[¡–U¨»MÕ¼FüVÀÝ(9.V§Ôð XìÁ­ñ~ë-.î ·¦‹ª†dÁ¢˜‚ö¬> 09@R±Œ‚ PÔ.,o«Ï±j\zˆÎAÕ¤S”Å´k*©Y}ÄøÚ
+5“šrµÅíã®)KŸ;˜bLÚzÐk6ZJMðYRжʓ8ŒªÊöë³p/’ "}Máv¡p›,
+ôŽ•ÏAçêö–]úÆæ/Þ6­ÅÐJÀ,óQeJF
+þüÏÖ±{ÕÇÐÿ"ܽ+gFßf1KÎŒ n\‹‹VÓ-Q;4µÑGL
+P_¾ ­×Jl…ïDЫ­TR{dÇgbšä3cßÅonp)‹±¯Üͬºc¢#‰Pªpª€ÓID¨Dbü¤A||d9è¢éÆ°¡pZã>'@O¨©9±xÈ4N‚›œ|o¹½?C
+ÐS‡,Ú­°PoqU†Ç@㘦’ðT7°f8à
+Ðè8‚µ®v´…ÎNÇPé5Kݨ÷jQ/æà†3tWa¡-±Ü8cÕh­nÕ¾”+$%-Vœ^b¯Äâ„,Ì
+¢ÙŽÇåh']ؾ¹1ÁØç:¸5D#CÜh€"¦™0Õ¤lW…pmhlHrçp0ϲæ¶É`÷@zÓHŒ¶ÏKgÉ!¬ÕùJÀ\sdÅ ¿[°8êÌ&s’;Î()D¢©‘‚ÖH½µ¼ìc\/¼NÂ/ .Ú€멹
+y‰Ë–!ʪ-þ&õÉDÒ‹1õ™½Û"÷.lS)Á4
+273“†ºÇžpÏ͈ªAé ¤brp€)4cQ0ªO!´™ ˆÜw°ñ%ŒÏ}ÇyBlyŒõÒcS„àD:2·¥{5¼t0¹\†>#âÓˆ}ŽHC«Åãº*–ƒ°Jè°'ÀTFú4Ї.™Óo`ŒÐ¨aÀjŸUsd'païHo#Õ!¥§ kµ.íóúa,;_©×çß.%¢ŽY+øo™ŠÅpj×_.U2}O3 ‘>¸YFl›ê
+‘í¹b‚šÙQГSÅñø—D^&:oÅ‚Äwy½‡Ï.üÛ”ÏGN£*§cÕ‚!oRxý¿
+H‰\WÝŽ¥Å |‚y‡s .øÔnÛm÷%Yå"Q¤ (H(Š’"B’ „xû”ú;3ËŠ™9uú×mW•m].jŸk^ÛEŸÙ¶uÙÜѵöâÀ<¾ÆŸOðýKÀûš²ÖÛ±[®1¶ÈþXz V ¼‚×µ'ûãíª~­IôÑ Ìž‡}}u«uù°õ(—菱ÿÝËx|þß—3˜?Æ%Œî3~=~x©}Ýbšá¨sóãC¢z-ÑWh`v98/š¾ß‚k_6´ý
+¶këª+®k9S[\ó5<µ¨‡þx÷ò¶K‡ìÇ ï©^ñÈ[ÕºÊÔ¡Ëmß‚ovÃù®±ˆ˜šÔÍDXoÓXÍG
+˜KÇ9@l îºuwP¦FP¼·¼§£<hæù‘¾¢É*ÈäÁÓ¬–“ ¹›ä£4¦< “$Τ‘æÚ#™gAºä`+Š8A·.|”¶®|fe¤ÄÌkZ658Ý‹žwO&è2ûð¦×™}*ÈìQcñ Ã"¢ªQÕ~¬*ìΨÌZ
+²žÏâ‰9nW|pÇ”aó>€zÞ5Øb{ká¦Ñ E¨ŠrT ™sÇoUz€<!eî½6h£@óD@Žd“dË}¯L J»ÅL“gß 0ƒyÞ`É¥¼©½ªg´2“«
+(©æP؉%Ôbå´4°Aq¨#[´vBmŒu
+„ J ÒÒÆtvu¸pWÐî«r]·³ÄXcír}¤qÞé(TíNUùÉ ÐØcé}Ž¶ô6êþáÝ+Q@Bk!ØÐû™Ò +‹5ñu›ÉyÒá ¹=ªÙý”ÁÎì¢ØÖsùµ+J¸†“ß¡“}èz—D8g¾=0™öš{²gƒ¥;óÇ`i¸Ò,0/ ÂÓÁÚœ§»Å­÷Ý;OCn˜Îíw©…áiäÍHj(ªÔèì¶q–‚å“Ôƒ‚Ã[ûíÎIPy½?^׎ãõX(C‹¸µb§U­Ñ‰Ýp[…2Æ÷`µv¹
+Zv•šû#ðG"r¡$A]>Y‡{ YR ícV¼.Ž_°Kl/8‡ôV08ÉÝ"ÅWµýÌ×ÇtÐÌ©¼u;5‹ ç
+ôÝe wã™”3{Ö}?áDeŒTõ~íUÀU"–'Q²
+ …ûá+© 0¤-OàÑfǃUqƒráz²¬b¤ØÓÁqiŇFo4ë
+Á·¥,ð¸:3 0rGßV]ØŠ(ײX‰O;úVXRk(ëÙB®Ì
+*}3ÀcÜêm‰ŽÑÓ8 ·ÂäØ2¸³®²£‡5£ŸX­Fët(Ñþ%ÛîF´¿ÎÙ7¼ï*ÂW ×° ìlxpXbm²›™7h¢çŽðì»h8 5
+(¤…´"Š'mªÑ±ëÿ¼—a«_9Æ?A¿Ã}ShAÜ$“L}Õí"¨ˆ²«X)¥[ܪ­¥tYúí}fž™œ[ÄWd¹Ûvî99Édæy~cã‡w´,6 wÊ zÌYÃWcf¬întÿ2›u‰ëG=y6‘ŽÚ˜>dsî”Ы‚ @¨t„´¡j©w’ÇBYCý¨â#E}QZš>œP¶Ûs·ºo;è;PºOal£&6ŸsÇzßG;:Í£VËGÑ7Þ×Vª¬_<©Áý“€´m{0¸ŒÁ;ij»sB*t>‰®ˆ± š˜ÍÐ6´EçÖeF䞇a MAeÜÙÃÜ
+g‘i(æ*Ž ÞÈW|.@2ÒYÔ ©qƒºÊ!†v·à´Á ž×M5D¦:ïzÍluw1Và—6øòÁ¯<|þ#~¬$á™îœ0͇ϣ1I™H€mÁ6ù [u‚Ú•ø¢ï/8¥Ü‚œIq3»o+®
+#3:%D©°Ô\ªÉaxrŽ²fÎ~­p&ʶÚAjÅ{Ô×A“¸OˆÅ6 ÉDâovö̯¡J*Û}ÇèÃãº\‚ÿ!;ý& lý)5Ù
+Gp8[ËqDZ°ÅnT“{@֊׶)J
+öUãö
+\¿ìÈ4ˆìk7Ž °ÃH.åô;š
+æXéhfÆI
+êÂuÐ¥ã³Ä¾p³„ý‚¾+bÝάÄèß,;ÂN|í¢ŽS%Á±‹€X\øg­oVÀ,5Ù¬S8à¹0{¯ö£e¾êv‚Î×
+£ÍÐ0ÑJÍ!òÖš*‚X¡-"Àš÷‰¼„äHx îH¹¯ å yµ›vÎ~3‚e׸ÁLZ[dÓ¶neóŒÅ×ÑÈPÈʳç4´Ð,ˆoÁôr…²óx=¦7“P1§ ¶ÆL»BÌaêÚCT R¬“àjÁÉe;ju'x,n­¢–o©­É¾‘†]Ôkä&»0¡¹ò1«Î›V-¡½³’´ì.8ˆ)•dzc¦èÙ¹––dì–¹GX£²ž—!ÝÁý¹ä“Ë8*›ó@n¹m—$´ýZ%wFW;ÐÜ7%…Ù`܉PL%¸*v‚£¤¹MåçHµ ¯¥mÊš¨1GÌ•œkX„³ÞÈ O1Í"ªœÏɤÔKb 2Vh!Æ‘ÿEÿŸüOõÿOøÙwß=ûóƒr÷äý?Ï~zð#þøýƒÏ@¬øŸöß³øÇoð—¿#ôÓ]¿ûÝÝ_þZî¾·—¿Í9ês\»/¬ÝÕî j÷Æ´{CÚ}í¾€v_<»'œÝÍî fÿ,ûÊŠ,òØø‹Ûñ|¾‰‚BÖ„°¦M“:šçnÆèuÀiD’H¼MÖÓ„|7ÎÊϯÅl¢ììÏu¼xCw•ó¬W(´Óí%c„< ˜:®ë\Ý1ÈT¹¬¼ù]ö¤ ŒYå,ÐÕ¾õYoêÁ³2ˆÞÒ[tN[`àÙÙwÓB ñT5@pc¦qØ…wñîèGÙ+ŠÜQE R·]†±× <Gö’ç;éf ŽUL»pÀdˆ XØ?†0®»æ³\Õ5‰×Š ×ç•E#¹ëãصô|”0a:ÊðÞ˜Á öF¾[5š˜´w Ö–8%UƒQ€Ó‘<´c¸‘#,½¬ÐŽ­è3˨\èd[ §Ð™9¿Òð±ê¦­šú±£q-Dƒ¯–`”µêL&;¦Jr··'Éךï—(AÃè\)+¦š‘ŠdÁ¤…™èf+eAèëêA»-´Ú§ñ¤â¶"¶9Ùé»FÁGâ3ÛÇ"Ë%¶ÊEàv·^¸‘9äz[ù¨Y@¸É&ÜúB—k…<Œ™îòÓ}Ü
+3ûÔj&Ò—VSº dQðÒ›ˆ¢‚Â*R)lZú¬ŸÎmüME˜ÖF}§‰>ŸýB”…ìœÜëÞX
+ XšnÚ7¹!&–†»ÖEÔré| ón=6%µˆYžåSe´]µ£%«áôPé–Sóüo¤ðÞcß°Â#·š,gÚŒo"f4Ý‹Bºë8t`d¸Öåh÷v(ŸDäºÞG:Õ~V×dvÝ[BŠI_e“RH€n0`Zßç$‚Ièç€.þ*ÆBTé;I&í:Wqи¾úì kJ1+À/5«´Ã–È$•ðÜýâ+T) ò*n+¢Ëù
+ícá|kë?ôs¥DÐ"*3Ž¾G5U“ZHIr.e•G…¨ÏN^1ü¬IÀ¦™©÷G0)Ì?ðÇœ -")ñ>E]4J‰·ÎI÷«4˜­Lþ8bŃ= „k6²¤•Ï‚#÷£×IKQ ·#Ä•½$ÃØv”b^¡bMî¬;uzuºÎ⤄ÖhzbŽ½ šÅýº½­N»ý¡e0¼1ÑM |ïœjoóy° Ç k$ß)pšäÒdìéØHÅÄç~|uú¥
+ØÝR•£uÙµ&P^/>€’^Á3î
+~Ï3piÉ3NêÉ3¤›“gL$zpðê)@F¹3…TX˜bßåÒU8¶Å±~”•1a¸â^Ò¨•ÂÛ™¼£ jáK 9TËÃðJ‘`Ü$”h_ð ™Â<<'>ic@{d®é×>'šÍ~S±£ÆIÀØ ohB(øÛ8ýûl F…Ô© Ø*Lt“ÐO°;sQ£µ‚i€.MêVI ÔïÎîEÓ±Um¨x6|vµ‚¿Ö“>>SšÄK*¢Oøq´û ®¤7.g¿z·jvg­´Tã&¤øKƒi<ÑË™q€šô] Пfq4•q2•·š_ØC
+^ÓÌb¥±«ªº¶Ôʲ†h¶­#ˆÓ˜@™ÇÙ¬¢Ÿ=Š=ÝîÕ¸Sáò–¦*¹ÊB[ …-­‚G¬ ¼$hÑläÑÕÖý© *2³°’CX¯ýa!ºÁù¸–†á”KV#Ø‚la6N BçØA7¸f¡#¹ØÛ»„[]¡ņ¡öóv <øÂv°m/µ%¤e,8{¢1V¿èš§÷
+Kw,•h†\ؼ3µ…r=ûU’sà]z^–t 8ÊE»;|;éLSÁ¿#QÖ_ \IhÌÃ8ËKB‡i~IFËÏKŸõ
+B {«l
+ÎÆ©Ú¥»A|ùðèß_<ûðñÅ»ï?¾ûåýëŸnEèñ³'·/¾ùøáÝûÿÞóÃë_ß~ùÓÛ÷o¾~ýñ‡¿¿xrû‹-ùêO—|ûé×·±èoøóðÝC3(÷?¯>áøë̓Ü?¹½ú×Ããgo~ùÏÛ›í»ù ÿ{â+Ñ௾~x
+:—+|ALÈ\…v:Ae}äâò)N÷¸P¾ö\9ï9ÒO;E0ù6iÁuAC‘‰ Ý v)®@e’Ü5 )6ddRà Ç 2Ø–}…CV0ÓRÏsK>„ws~0«‰~à>è6ÀÚñVäÔŽÚ{ÁA¦ô)n•&õèuõRŸÀHB9°ïqš‚ð‘QRâZh‡‚d;“°„4q
+”Þ<]ÇUBŽ¬W/>`‘{‚:è ™KD'ƒeÊ¡#Oé
+N’ô q6ðÞBºB(Î s8 rפÁ¥4°qó¢ÖÂq
+“àšæÀz㘚¦+ ÞBøUu<˜H[ùÕ6-5´ !]u­ãªñ£‚ÓCÒXN}æ“P÷a =%<
+M=‰8Ž*_ -=I0‹<[]À˜¥S‹ê
+*vqºn
+j•‚~'d¿s8EBÕ»¬h»ÉÍ ‚ ³‘X(2 ·J^.…œ©­ §ºÏ´i~Åqq Ī5C“U‚Ǹ¥–£¦;mBÛ;ÑèÐÄ´qš\3¶ÉÆc¢O]J[Uõ:´Üp¼ÐR{(XoÒq@"!.u0ßQEíbš{$4a8© ‰8Haë˲+áBi‹˜ì¦Rh2[³0”Ø).š.ù„ãǽ
+¢^kSÆPözšj‘«þüÝW
+r&?öYøRZ@t&êD߉VךѣxQ%ZìImh’ô[ª®&ÄÀÐ5kReÒ*
+q2bíH$ì8én)"gJÏ€Ð×%</Ô°èÚ@,ù‰§ïCpAWaÀ},â8\ZF½IÛ ¥»IæÄ;&xj’PÁÚþlßî†D8ÄØ5Ê
+†î„b*È÷ðÎ涆7é£ÌˆÊº“§: ÈÐùŽQ­bËh¡7¡9óhm”„¶iF5”“*ý/¢ÈLÀRü~L#/O¾]ZwªîͨoŲ̂ڵzÅÜσ)ÿ–Ꙋs)ãN„u)až-Ö¥€y®Xó|ñ.æWx“{^áMîùÁ»˜g9aÕg9aųœ°ây…7¹çEúÚbÙ´Ü0/Þ—…ùÞ¥3Ùx_a]zX_öI†_“eãb`/æÅÄ:Ì‹u.˜çr×rÁ;WÛ’…y>w,u¼/bçBc^¨œz‘û¿ xŸ÷v2±.æs¼k±±Æãx…7¹x“‰y!±~ÙñÎs2sr¾v¾ι`xÑwæ㟹à˜íYè|[¨ÜZlÌ }–ÖdbNöYNXóË>Ëç³Î’Á‚3KÍHªa¤Ff£NÍÊ"/+5œXõõÜíM.4mß”I 5wÿO~ó駿ýé¡Âô7‡26_|ôøóÏÿyà`nê̘ÃYåßýû‹/þó´9#nöÞ±Çÿ}úôé“Ïn•¯ÄœtÃßž<°íÇŸüóÉ¿fcø¬zè/ÿëG³ðÙŠpz¬ã£GN”&ŽÅŠ¯­?pë—'íìYÄ<Q10TûFʬr'«ìêhkAÚltC[:ïunIà/¥›VÐrûZyöka:3¦}dýþë7ªó×e˳6#e#Á¸ë÷Ï×TìÁóÎÛî×f0Â|ýу‘¾^8×.µ²ôt3·^zôɯn$Ö…Ê\óÍöû~¿º"†pêÚ7
+Š«oܬ-X—À¶HznÉ¡êíÕƒ6¬œÙg‘ôªöž¯<Ô{»%Aä"éE5Ýwoœ)-»x¯sëšHxâL½=œ^XÓ=ÖÓüVnqëÏV•§¢'+crÚ…R7Uu]©-ÎZýõýCãçÈ‘]ߎ¾¯ÉÚÖy·§¶ÈˆD²ÊúŽ“ƒ{{ûúúú¯ýà»ñJËoi-6"jüLï`?éöõ_ºuçÞ…mY‘¸kK»îw•fƒ$”²žïÒïï­jê8qþÃùcB©yŒ©§d¬UV}uƒ]Ó~ æòxWip1¤x×áÊú
+igE2ÅÙËÍåí#qCv >ìÚš95 !CœûàbE奸‹ÃÙ{ÇÚ¦‹Ê¤Òo–‡”nÿPíÆ•Ó‚¹rcí;½;»Æ»v¬[•¶2ôüò…{[G¯–o(l¹}œççeC†ªò†7œ)å_É.íúðÆ÷Žú–’¸FöåLmL‘œ}ý£-ùi«òÇþôë›çÞZ–å;4Ý8yE?\pa|ìj‡k„”Ó.ïÈž|qÞ–ÐB©Ñ=ÇVðõXQÐy;wGæCœªQÜ:2|,ò&¼úE›r¨¢ óì¹ó,ˆ{Ä(ªí¹ÛIiNY• Óï0Õ¯`þf§¡(Ž¤e×^ë®Ú¤Ò1ÎYBcW[wäd¦­LËÌ)(míë®yfƒGå+÷ï\n«ÜZ°±`˾–®‘»Ý5EÏnð¨Üóû?Ž¿ßÕVÛÚ90zo¸³ªðy³
+ú›†.z \éãIºøRØÈ/92µË­šðŒÎi4DÄapËî_Þ£·+Bb«2CÞÂgÄÏý»v=EîHl•Y|³ü€NØ·É&
+}¼ð‘ŽÍ
+‘<µ¹µáe‹×h«L‚uÞ­ruDèØJ)•d0R¡8!hámB7í&VT‹Ðbµ8Eƒ·þä’1¹ƒ¥aUÆú.hfthòåÄ­]Š¢P“¶âÐ%£ Ýœx0u ›ð kªfi ËÎã€HU4ÈQÔTX
+ âŸ+,M< 0\µR¢LC…Þfb+ÑMÁX³‰
+7ŸÜcKÓN¬j
+ë`MS•¦5¹/ Vø½DE¦V½asI†!M)U4y@Û"Tùý@~ ¦P«^—îçúñH©TÍ|çVÔS%2HI›§bjÒÄ¢D>pE“Q¼ ½UÉ3e½ è&혃R4Z ¥ÿÒúBχ°ò-’>nE ’W‚§?„+9º.¼õ\QÄ>­5dú¢ƒªžV ÙÐ'JäiîÔŠlîÊK”#¹˜É¸i»+XeÎmê´yÞÉÖ|-ñ
+¬½aX¸`mFÞóâ\cŠ
+Zä}ÝÖUµ-‡+pܘòZ´~#Ú†šS~kã$çÙÜŒ@ŸÖÖ¢ò°öu 8¸´•å‡ÞZ1rþ¶‡~,íuTÌÛY«Ná’ì¹Õ öà Y¶–ôl¢Fó?ë$Pä}u+ ²iB_Ÿ$fµ ¡.ZëB‹-<¶'½yÅ–3ÀŠƒxᱨÖúèw¾ø^æ&¿¨ÎãÈuXšUÐ"ôªÖz;ŠÝŸ,nuƒéŒ_ñ.\÷4¯Eáw}$ähC²e"òªl´=£Û}ípR‡f+àÉÛ¾àÜ|O¡ox  ƒ¸ÏFAè€3a’/Oê^-íµnkKO¡¯ øñ<ü€¡;¸~Bß¾cäÀè×¾ûêÀ1ì½A5T_nÙ€@“ ª‰Ø»ŸÌï9~ƒ„ZèM µ.i—
+šO‚ðW#?ð“…1€Žå¬>ô|§ 0ôæ¡À8rhx›¬‘ÁŒŽ’i uÃcOÚ£ÏÓuƒÐga´f‘•Ù›¬JìdÕHðx™GS4w ùê¹µK¥À=‰FíQ껂ÅíG8ác9ÿ˜©3T‚*­óÙÁ,# ?n sÔ‚òÀúY=çÿ&Œñgà³Å(˜ÑÀõnŸgTq¹“¨ws±]Òx²V~
+s%ŧ8¿Séi’eí¼‰DÖÓ©çN2÷EEþádz'Å(
+Ñ|èÛH³$@+U'š` lØðeëüS3LûÞbñHtZEBTÜÔg´ÑHjQj7þÿ-×G³}¯
+è¨?š\¿ø3J•( óx“MžÉ}(@ÁØ"p'"üCÓøõ·±µ©–éG©e'¬Òx2ë(ÈžF9èq?‹;—¨†_F[׸«±f£Û—Nºú‚áz@3¨¬œª`€ÝºAè³0*+ùÙòâQ؉cÕ%§†ÔqçÑ1éPÂìRÏsÇ!MàžÒ çÑ]AÌ­Þ(W}]6‡¿2ÞQÈFRÅ
+roö©Í€Y<
+eˆŸëúcT?w£™Gò'k½ï ôaר°â>ƒ&†‚ÅŒ{RCZf&àù:k³§ôO=O)¶aÚàKWEc´Û<“4MÛì=ë7óÞÓh/^n›7ÁM Åh0¡NpWqÿðL.'ŒªçܣۦΛsÛ œØ´ÿ±_m½MÃPø¯d“@¼b§iS!!uÝ­aƒ{©¼Äi³¥Ie;{Ø¿çÒ\Ü‘¢lhÐ=­ß9>9¶¿sÎçN»™‚ϯÑ{Ѧ}Ÿ\þÆi(Çæ)£TƒI b&Z³²nT ȆC7ÅmØ…äæÌ5äœ0ñ ŠÝ§²
+†~(ó«ÅÃÒ[¸Š)â™
+?Sú±nPÓã"Ë›c L\5dÇ&¡Šm[ÑJ°VÊ)ÖL°VÊ)vßñ¶Ë‰GÖðc¾–·8?» /Z~FÎiGa”7ÞôèvåI±Ü\â,°"Èeð ‰üî((O.§³™mC
+§§[ýOØÿI'´£„qѳBP6;Ð"kªø>èTÞ"ê ¿ôèJú7' —ל>¦¸q^y`ÙγڞÜð8L„ÍaýxbÙBÏi°XŠZ„ –¾Upã:.ÐiÌ"Ê.ˆ$¼û,Ý]rý±Ly­–>Ç „lùÄ’¯2[þÚöìþ {*‰’‘û #÷c¶j»´h'Ä]*™]YÜÝ¥îÅJ‡ø^Äk|[Å÷ô|îˆ6zUAe DÉj¯*ÁbB¬ƒÈ‰ƒ¨`vöIø¢¯ò¹Qe± ‚ÎéD W™R‚“<AèÛÅàwI9žTLj³Œ†É¡Ì¥¤écQe¸«íá®”áõCh;ßUà“Dw 蘴%“¸wð_ÅAÁ†ÜéuéÒ‰omS7 c#Oæ~yÒï_hëØÉzÆV:¥Ÿ_rï }_èûBß³¸Ââá#4Fº1áí\F[™ŒŒÝ©Œ¶¹íé˜ü=8”|Ú7û]&æcÝ^·ðæñ³où/ å÷_çÿT‘ò}N…µ¶l]jͦ¸Ïº½Ûe–„Ž}.òÂÌk8 V)€óJ½Äz'Š^‹²hí`í:Å;Øغö¨¢òáð¡Hë]ˆÜð8L- %eÉ
+üE b4œ”(Ú´”À[dµÿûmÿ`
+H‰ÜWkoÛ¸½@ÿÀ"6Ø’c;Iƒ~°ó覵¯íÞ¦(
+ƒ’[JÔ¥(§î¯¿CJÔÃïík½A]‘sfxÎ̈¤*·¥h$” x„W‘ õd€Žúµç˜/Ð+T¿gbHÆ]âJ£tO¼_׃a*o =ò7=.`ðþ„©ÔD}ô鳉\˜}*Ü£ Ë't‰ TÉBVa`ÒeŒ¢ÊûˆÜÌIðàºrú)ZèÒ@õæBN­}¬wqä9©˜WF¥KIà~‡^åŸ ÞOb®-wߨ¨ß|!N,)(ƒò5êïö¤ •÷A€}`tÖªEx”Ü:À{N4¦~ÅüÅ›‹¿ša/Ø.~Õi$°ó¼Ýi)Ç#ÁÙ3ÙêÒPÏô§Ø]iO`5ï
+Ql­B˜ ù_ÖrÅ7öÄ[è{õr¿ÿMZVÕq™MÐ5g!Í°Ë^ªŠ[®ªR²Õ4žzXIÚ¤Kö±'èÎê[¨>$˜ÏÿZ-NÌ éÄ] PŦ[Ø4ær⬥g\ÌŸU:jù ±“º%)
+±§Ü~Æö9”9;wölÆŸ°y5þ†ÍëÌ☊υ½käù!Íö®<Õ¦Jõ0Kü»½o¥‰Àž'
+dŽ:w“›Àíp¡–‹äLKÎLîY0à€ö‚éÉI2Ý%пL‰D‘Ø*²ü«|‚5K½Ÿe-#cevà´T>Ì<A
+“²— ³°Ç‘µs9}n¡US[šÚkMMij®55¤©±d²T.Á0„鿶L}”¿ÚS¾#ɵŽP„™¯åÙ“ÂZÉ? j™Ô’ÁZy¬F+ýQL²X «‘Æݦæô† «×ç%R ß\â¤m³À
+ra–YiX³È«Ö@æ-k˜91+ e&ÕŠ Øe¶úy •ªÓ³ %™¥H˜ì¿>=«ijY"2n)°eª„hjgåÀF±!#ZZ±iÚ8¦Dç¿XÌÓ•jfXI®\ÒÆRM3¨ªW¡¬ÖJ]3¨$X,®^h5ÿñ¼UZô…NcB²]Îc†U<û¯Ûy.ÕêÍR23´ÊØRhÊ´œÐ ¬È‚¼,©ªSdçk̉êÅbæµ½m»/§¤íÍRÛå,´½‘åU—ó#¡T•úU.ÕoÅX(ØŠ­X¢‚±kÃm,Ù–Žõæ ·& i&&êN!N·k,A“zƒëÎd0e:F
+ˆ†Øõ0Ýs­GF¡>ë6†Ð Ìk¿-EnuËP네Íq³ ;¢1\u<.£8ˆˆØb q*}âBF8¼Ph ÷¨¨Z
+’ü}*DüŒÂ ~2à`Á±ï¹ûEÉÁÒõw…Ã|{/ßZ:¿ƒ)•ßÜ ÷Ô°ì"Ã$ïÉèýb”ð2ÀöYüDñ”ìå_„ƒû ›]ô ¿%¯Æ}˜\4”E_5zp-(^’îsåøªÿñ]U®—ÜÆ8$UõíÅQ7æ‘Â}ÄØ6 äØáðHè¿Èq–qÈçjzN½aLbÙìq8úêmT¬“P ‡XDž«’ F0;˜á@àÈÇSÆ=G2Œ#á9h@`‰å«“Ò³óö´&/–™˜F ßfT†ùWJw G‡¤ƒ2É犳(B¿cáÌ`tKáºÁ¹”,ç!,ðOè÷¼éLØ1µá¹:¹%>V’kà5¶Õ3ì¨J W™$ÿ™‡«ro#„:3¸þ£Žã(ª¦tA`‘ì·ˆ>Ÿ$_Co8‹Ã»à‰éÏ©’ÈÜ3Å uyÍоCª¨ HMIi„Kw½u ÀnŽŸú¦Ø#Gñ[¹lÊ¢¯ñغ¦ԛrΠ×,³Æž­µÉwë‚Ðh°od1Ô0û{2T—x‘Ÿç 0“?ïê(”üÜŠ²0É0çÔóæzr{0Øn÷ ¥U³7ÁœPDf3>2?`n
++Ù=Á¾ÅêkF—ù!‹å'êLî_h¹åÅI÷ÊkæÄ> Ä5Ø8šÔõ¾
+å¨ðy+ÇýÞ=s‰T~sS,|WÊSö…ÊŸ
+8ãN£øéú㎘Dþ7—9±O‚´“Û­Öi+Í_çîa.l†¹‹FGâú¶µ9åDSÚ‰µ³âµÍÆùfh#'°Y °k¯œ¬«°rwI»à8ˆB ©w°œç¢Èûš,ÉK-! £S,k“@NÍìô‘ ‡X„±@C ½¯
+ˆ†$b4Î}¬VɧO¢Ùå~Ð…
+º`—«‹\Gà3¹ \ív6ŽŽ`FùK{kÒ…–
+zxA¸a¡äÏ„?ù{v,Ø£­ Zr¶g…EVõ‚äìkÎÒ÷w× <ÁN
+ÿ²z{j‘ß+Êá?ïÚcÛ3 Ù_Ÿ«ÕÃט@øl¿´°ýP¹¯bë1ç“'‡_ÖÏ®¡ŠC┘zMäXZ¿¬®ŽNOÎm¡Ý~©V¯¡ÅlwŽoí׉˜ó?¯šoY|ys¹ VÛ.vÇ•¬WêÝ«4B‰Tˆr¦úp£QRn!CÛXã§pg7ÕÀ<‚:³MÎ3-f§8p3‹¡XŸÎ¦éϳ]©>9;éÆ×™®öË|WûäzØl—{>ßåºÕj¦{ý:£ž_`ªžíb/fºX/ä¼^ÛÎ|¹?Û-æ¯W·´³†ÒÖÕ(!} ö@èºyWNûnÆ0_Q{4‹ïo®O/.Ût&MÏr¯Ø)`<}{1Û½~˜å^§W×óÞëÉ|þš57ŸÎ”›WWŸaK˜íZ?Î'ã¬îz6».î6 &®PÂ~‹‡›Î<F±ÂÀ6Æ2žù
+ÙËY<÷ûÍÉå|EìÕ¼i¶{íþÎÝü·¿K7DxÈNÒBsŸ ¡_HAðg<óeÃÿfqâÛËÃwn)˜à½—•éÿtÓ^,@öî⻯#iÓ‹¢ˆjNmÁδ –ˆ egoX€‚aó>¸zùÑãqýîòðø=\½fÚï¯M»~öN´¿›wÖÑ_˜P)µœYMâ¯íΫ5FÃ8¶ÖÔpd”E¦ âLˆº9šÈÝLåvïæ·ÀÝ4Õ?¾{xyýøýÑõû‹‡—_ëï-”ýý_õêáñÅ›“ƒ‡ÏÌqýõìä zPÿÓž‚NŒ(S¬ûkå¡ áé M€ÚÈV gX€‚álò0š~-‘Çú‹cZ6`Æ@¶>.OÎÝLåvï\Pž6/¨Œ²‡j…Ñ:JE©â ÐÞì!¼Ó§,ó ¥P;˜˜ÂÚ’\)RGSM‘aX•ÎCô •GöÉ3º™Hísm9mÚ¤0. ”M¶@¹ÊÆ”¯Y¾²ùd2¬€³1eÈeˆDJð6T±I/+Ã0œ7„—΃2ж Ö4Uæêf"uÿ쥅±Õ
+#¥5ñIc”‡6²õW—¹ ï% ÇHs-}¤FWJ’ÆЮÎÛ¤!N–4·P7©Ý»ÔFj$03µ6n׳‰ÊÁP{¥`VgW¼彤Ñœ%hRD:â$iìqBŠÇAM1K•gn&2ûW/§L;•JÌãr¦ì„ŽË™ƒ|ñòvCd9“r†!È@¨ “ %Ê@´c¢dé<ôq AÅ8Ïrfœº™H½x9aÊ]$L4DƲôˆ×À €½šL͈†?P[ÙK¾•Œf˜hÑvºÍÏCÚèÌ 'çQêfuxårª(ɪU¤J»ñyØ!^g4
+‚"†!Á™‰¼ë/€3
++5»ôbnâ¡ÌC¾Šy»!2oak‡T“|+‘†*e˜ŸJÇ™*ŽuQ™q3‘xù¢ÖŽ[XFII,•ŽCß!!?œÕÙ›0rÊœÜAܺ§·‰ÇdL`§ËùqØ,•—CO§äqâf
+q”Y‹)ÒZ4Ê“nà¹C|N8£°7GÔ= 8å `nÒ»$z@¡L ŽC»=CF0’ê1NÜL Xpkç,hçVYE"9ä0PMí¶¸ñVC$XÝGpˆ ZÆŽèyAÜ›ïQFúLqâfq䃥‰*VºzÐ|;iëSo4
+Èa4t>iÆò#'n&GXpî"
+I¦âý±Ý<ŠC|}òVCdî’eTz›DWNÅð¸±ë€„9&INÜL!æË—¬t+áí$LbÍA›
+¹08.­,œ X)xÖÙÇ™›)ÌÁ‹ õ’6/8º‚CBçpVCdž^«(†ÝÀEht›D Þ„Çu›)Asˆ­qâf
+1ÿ½$D<¸Ý·÷ í`)%<8J‹,ï# „#hÒ‰{Þ´§ÀˆJÏw-^k»IÖSƉ› ıÔE‹nG ]žHjwÕ¸Í{(´uoX€ööz‚'u «€°Hœ^)Ii›2x5?ωÍmÛ½ÖYo§n¦P³oÒðí²K©ŽµÜÕ&q±‡"!œašKÛm™¶ÿÓn¤ñ•mœ'dxÞp«$HS]ZUÊÔÍêÈ ç®5È^Ó¥tmÔXã’Ä `x/mÐ(,z‡$WJ´áv·rx¾É¤ÝZ²´¹…¹™Ä<±œ4TÓ.ã»j›(Ó!‘0}‰µÍÞÛ•0oy/eW0èB€ú4p=!UÆ@ü*hÙùöÆkhä€j–Ë·p7¸ÛóþåË©ƒë_+Üûç§Êy ×»¯F¢þ\Ù»S"C’R+ŸG|5ƒ”—J©Ò[mªý8Á튦0V~äˆ÷%Ç]@z«˜)ÜÉ#þÒ Î -!Ñ”è×EÏäogœ™1ùMÏ3%»_WõÛU/!PÄÕ­(:vz€‚©›†‡HÌåûQàkQœ¡˜ÍOmdi+hŠØ¨VH›Î-h«v€ºm
+æU²çU?ÞùUqfFÏÊÊßy)öº­¶¯"6û$ßó¬îò,ßÞ‡õÄÞ…abÏ#>ÖrëM6.(÷¦ˆ§ƒ9TïK¦Ÿî^|ñ5…tr !ä“?ìª/A¡‘äLN)ƒ°2rϳßïYÖî¹y
+º·r<¿ÓÃÜø]xØÿ™¯ÚÞ8n#ü ô®(\$@µ^¾-IEëEÓ"èµ ü‚8ý"\¬‹-À'§K\ÿû¹äpørÚµäƒóA†ô˜3ËáÌ<óL)Ãw…Š›¢(¬Jz˜›ÐLÌöì>MÒO'q™+VBIQV «õ‚dLh3×g¯î®%×{Y¬{A„cDÙKëõ±5‡ø !ÉNÚ~Nküt¯üv$LR¹%í’(¬ rǶ5½¢ïçê↑?\)ŠDáJÃdÌ”ƒÈR» ULˉQ½aålp?>087…¤ Áù]JÅÙûÕŽbá|] +v3ê/Î2Ó‰ÁÎÉÄ— ãëWÏÐì_g=,;&_ТÓ}ïªÞq#Ò’Á[H°rË_Ô¾É"hÅH!žPšyqÇÊ9KÉQYµ `è½EªNKÞ#Œ%Þ„ÈÝ0}É[‚ДÞõRÛ&D#æc|4)BÞ”¥Ó@‚™ó…$—^-AøD™Ë6D^MˆÄ°äf Â{`_¶ r7
+UÏŽb^ÀI|¤ÁÖ˜ÎÓó¦tiKN(òÉ…‰=çu'ÁPeÖ~ÖÕ5 ^‰ú ý
+}aõœ¨^/n×ï]„ZMƯ>‚jg\+•BÐ+ƒÍª#BDœDÔ ÇŸáƒ_½¼¾Þ춗«7ûÍåÕöú°_ûïŸ[ÿÖ!÷ïø.yÎü$¶ðUã41ƒ®Î¾öjwvÞÃü~nŒ.ÕCÏ—nvñ¡Ÿïøz¡ïq‘o'xþ|ãÙ£‹ÇO÷‡¿_½>\Ý\oöWOTüýÍꫧ—7?o/ž~g/àøóÃÇwÛ‹tèëÕŸÝ)ø) 6¼’ÏÜĶéÍåiÐ4-ˆ°G«@°Úmâ#(öACEæ|!” hW#ÉìH‰°~ª‘v‰Ä,‚aš¹¤€ØÌ/D*$Ô€Ûuëóbc@g[=UÈ×ãB×!ìÓ•G®/üû ížÕM‘…Ìe3Õ^8KHÒ앧ÀY0îã¬ÓU@|  oΰ!#dÔì*ÚçÉmNù¨†S$ŒsñïTí•—ðfÀLÚ­‚GžëñÅú××W—›ç›ëÛóßo?ì¯Ûý
+©“ªîœuƒì‡Õ Wi/6g_ݾ~ûÄý<~üëívÿäýæööooonOü~sxû‡íÿû ùõ×€ùmóî¯ñ—?Å_þøË~“¥ëÍZôþC‡sè7­éÌÑÐNÆБ’¾`U#ŸÞ€äȾ…d‡LßCK˜åÅyÃW¦‡†îرiSz—x&1žLÜ‹kC²â‹—êÆ
+%e¡ÔáÑ®Ff¥þ§oƒð¶ôcù2ب«Î¨ fy”ú \‹\“(O—>À'zªš¸Úq¿’ !BûÁ®Ffgô²% ˆRr(W'VX~¡l ƒr†™ÊZçám‡¡Ó X'ë—;\ ]Ÿ~XçTíÀD=Hó-Œ&ÅO›
+3C>MH•}+/
+>tVÍらNö¢½56 ÿJ_;ÕF€)o½lB Šƒ©N
+1ÐÙØÛNj ³1BdFÃ4;?¹<@5eËu‹5²vh熊ɞDžžq˜~ÁÁ÷öyö¹«S0åNè& ï*WL½•”=ò誰]̲ÇBñ4À–©¦¤ˆüB™xb¼Óš©ÖyÈ°ì;iŒ(ÄÓq×ãBבoN'j}¹¦û†e ®É¾¡”†hW#Ÿ+1NJJáþÓØüB41 . hò¼õ­Þ` UÛv=.q℉‰ôë7TØBb¤“[Z'Èo¦LRÞn@ÉðA©QàFÇ×n,¿YÇô
+†ÞÝ·ÐƒÄ R”6.d~t
+éÙ%Mô°²’‰·°rñ¾ƒswo\ÿ^¸qµÃ«ÖÉ)i“±KnŒ¥õq&•$©a‰T|3!ýpŸ°ŠêŠjÚ•ù§3ºFî¨J’í¤„1wwHφÔÜpØÐ;ñ†Pê2Ð:A¤Ê£aJ†¢L óÅI_·°üJiJÓY&Yë<Ðæ
+sh–\uB‹æÒU;9Ew²RÚ_ÔLŸâ>зª—¡u‚¦ÑI24l@Éð³eDù×ò¤Àk„6MéêÞ*{<1¥ïq©ïç Óãš@[F[EôŒ,A[%B¤5‚]Ì/<lÑÂ#:ãä1<7ù…舰&2exëü¹áJx†É÷ãžÇ…žO¾ïL ¡â&Æ=…96bˆ¬™êJa)Û‚’݃òÂø¦œ8DdÊÒ"Ýlv¶:YÑ
+^T“û¸ãq™ãó©³"©¤šZÃRI…PšzhØ€æ%Õ¢¼$!3U©mK*!Üá¬uÞõ‹ì”‘Ç$Uåy\èùw ©‡ùMQ¦#ÝâEAÏšP0t¢*J…ä‹h‡ÙVÌ<ÅK ÔKê:´lAG¤ÞäF‹z±qB/4I$@Ðaé 9í?Aá
+†ÎÛË3n~[==ýN]|{}¹Þ|ÜîÏÏÏ=úaófûb¿¹z·ÝŸ½¹Ýü¶]m®¯o›Ãö=ü°õööp³ß®nßÞ|p˜Äã}ûý?Îþ/À
+H‰ìWýSSWöð—팿8»û“[@\wm«@Ñ]· ?@XD$|IH HE>(‚|hA TŠËâGk5PEªk´Î2»m׎caxË™ãM¸YëòÎ3gÞóä9ï=÷Í=ï}ïä¤ó-77÷DµoÃÃà y­…¼ÜüL«ÕFEE-Øå’’’,'¸ÖB>Z¶vÅò·öª&§Àpô^¼ÚÙЮýßÂÜYœª6/ªq
+¯þœ$‹<Nƒg/\äÑ—sº@ž! í¢!U 1›öçš°ïKsçƒ| —4¥¦lùÙ0'=Ä
+zŒŒ©:;ÉNüø@Ð ¨9ªØ»2µìðõÞAŒŒiihëë8=|DöR\îjûϳ@Uêú&à>[Œí–‹ƒC²TužÑ üÅo›ÞÒƒŽH¥RMqŠ4i²)«€Ôæk?éìþ(ZÙ´­P©àäîW¯X±"Eª$æÒôO a±ÆV@‚åX‚˜‡4*ms†JÀٲ탕ôÖ]kâÁHL]’E
+
+jUˆ_¢S—7¥+Ó£0&¤D*eªlÕ‘XI\h`˜2& Χí½Å% 1=–V½;(ÔÇÇçоÌ=~{ ¾[1§U |1pP¦&ÒkõŸŠóK–-[¶'À/-.¼ IŠÑt88€<,ˆÈ§cÃÿzöÜow˜4U]ÜÔºÚëºþd[×'!áÉYÕgÚ¼¼××7›{zz¢¦íò¹^w7÷’Œ
+
+
+t:F“Îøý½'<À৪¢2ž¼Ðj‰Q ®8Vßn8MjÚGOïð0˜êÁ_èð@f–zŸ<V@êMuÐ÷œ·Õ«3Ó
+°)S²†ì'ž[ŽUå%G¿»wᜱàÂóïÿwʇ†iã™®îî†ëÆ|b‚ðc££<9`µ‚Ä(§|,™
+ØÝ5ñ¢ øѱQÌìígé­S{Ã(d¬Ö‰í§å3fµÚ—Ùò´prâÇ—cÚ–.]Ê:í´á+lÆ ä£J`ŠÞ ¾ÝŠ7¬â5ø”£UBéû£¿¿ÿl- ÅO±„–#ÖÂ߉m4î…ÝÛ¤ƒ¡P…˜ ‘=
+BÑøtÄT<”|@,A@Lá dÑýÂÇæ!{é]óù§?‘^XK‘ioàé/Å_bÁ˜4câ;­%ûÁNÀÐnéfÏÿòåËÑŠ°{$£;EL–.zÒæºÏ³áis\ïx~æmÞÁù…xZætGÌðœój¶úCgŠX"¡è¡"›¡g­bÅ?Mˆƒ' 2šÚÖŠF¾àÔÓµ÷È6Æî‚Ť-±í3¿dÎð7–
+‘úCeœþ–XA(>ÿˆIS>9¬þƒÏ?4|þmë…lr¶SÏoŒ¿ (“þSâ)»îkeÎ*óˆãÄúãÜRöZžXþ©¦SÃ×1:àTÜÄó€’olë–É×z“²g˜ÎÎ)žvZ¯?TiC _Qêœb|+ÅŠ«É”¾ÿ  ßÛÖŠ’u ,íTŽXùäßñúCõE¦Z÷ªr·€æ”ú㔦å 3¾þØíXïDÇAäŒÿbš´WX'Æ·dvCÍ»þ°žWd7âÔ„9ÙøŒÙö?T™QUØ‘ç¿×DBMÚ«?”%V¨à8²1Çë]#}¯½¶}ÑØbÿ㈠žgª0tÞéóßqô«H(ñúCÕ€âü"š“0>iÈ-j´ þÓ†ŸXDÀÁP“³ñt°W{{¿(sî1ƒ‹†mÕ´±©àû‹º bø—²ÝP‚RÆ÷*üÿ6×ëÍ3Aý|±œ Q¡¢-ÒÿPIaSVˆç#°ff®-Ö¢9n‹9tįTÖÿÐc?§P‚÷©ƒõ#k±p
+ºfÌ9·÷ÚŸÖÿPNœ9…¢Þ†M¬?¬G…€ªÐÿmýYìmÑMÜþ+À
+H‰ÌWYsÓJþúz„‡‘{_ª¨T‘.sÉLŠør— "ȉAv[a¹¿~Z-©û´ºÈ!TMQ(ö'Ÿýô×çÌŽo.oWÕº9¯šÛÏù³g³³òªú÷fyµ\çåÿZkMòw³7·uµ^¢åïŽÏ²Ùëò{µi…ç˦®ò'Ýwütöv¹]¾7H³¹­òÙÙ¦ú²¬¾ößN>,›¾\®›êCÿíx¹Z™/‹²ÞšoG7õÍ&¿ ˆ–S"…Êç”—uÙtæúßÎ7åzû¹ÜTëËï=vppx|”µþ¶ÿ>™OË<›½©ŒèòKe•/WU³Y^æ›eÆ°(„f”™ß®¬TmžãB %¹ùæ‘F[‰ël‘Í^­ÏÊæz^}kÚ䜖ÆÄ·ü[7pN
+E™¹ÐcJRÍÛjÓ,/Ë:p\ñ1„˜XyÈÉÖY¤ÎCÃß:ÖÕzºÎfgW‹üðôÈ~xq³Y•ÖãçõòjÝv‡ñ¶«ôÑu¹ñïÏ—W¹ê_ÍË÷@ò¶¹9^n›r}YåT´%¼µjfçÍÍçm~q1{]-šœ‰³KŠuþ®ë&Ô§çSv87Zçíçù"SV>k¤ „š0ç«ìÉó§óÙ“ÿ4í#‚;\H‰…4bóÙ“­ýÍ¥}^ÛgeŸ+ðÙþædžô©°9¹Ó n>Fˆuãð‘ÜÈíó³}–ö¹±Ï¸g]¼»ÁŒÑ‚0*xßæðìh1ÂÚhFÕÊô˜Çœt%TBp®G–Ì*PoËkÔ·y}ÝÆ
+®0g}ã$c0éßÂÔ5>Õ“*ë}°µ|8PÛfûzvµ­wÔ|Uxª›Œ‚Tý«K•¡HÎw¦
+!Šñ$Ÿã,#Ïðyó
+.Éã >ÝúÛ8Qá%èÌ…÷u?Ÿ,k>Lð fe òñˆ>Y&|zñÃyú‘³ë“<½ÜóìnÁókÔUö÷ÆrßàÍ/?\µGÉ zÕŠ£‚Ò»&ÆÆ“
+ÌÊžC^bLÛATÆ©BpFÁŒç 0Í%ç¾Ô8é›ÂS@;œð~»£”äòß1™÷ ›NHÞx0Úý>ÁøÏ„¼SÁ ÷ÇÏrêZß»ÌnvÍ/”àdgó#Å‘úÿ¹æã°ãô0lBåŠÓã pPR'*qP"mSÎŽ× Î_}Öãt÷rD¤Ëú"Ê÷Uû$æD *î^Mº)›¦Ú¬óËm>;CùöÒøÿòåWÛ̬…”©\’‚(†-Ù Q`ë¥k
+;ÙßQÒš6·Š
+‡Õ
+:ó"0o²@”‰—¬À’K$Þ Ç^< F‰—qnè”0†Û æ"0=`Àˆ“Ma‘]Ø•¼ÀØ°<´kùNË@ÖaÀ†“Ma‘]ØÕ¦˜( íjZJԬÀ '›Â"»8ä3LU¡´b p
+6w!šiÀ üá…“à˜UpÈfؽÖ hšË~榭8á$™­;0RJ˜o¢ˆ™{uÐÚ¹õÂI0ÎøˆÐÚ–E\ƒ¨ )„TAЂt=H¦°(âÉÄ0×€€»CôÈ
+‹ƒ 9¬ëWÖs4àÍ:È›µ4‰ ƒ×^2…ÑÞ¬ˆÍ-AŽG'‘D¶Ë§“LaQŽCÒÂX‚†Me"¥AS9 dÔɦ°8ËjtˆƒÛ]И˜¡iÜ°wñèlÁx(Ñ)ë°Ä6BFƒB9 tM@Q‰É˜¸œÓöã°³zÄ7Vw2Å‚ÈÒ¨—î hKñB¥9¤­Žx‚îòd('œ£9,¤­Ž ‰ Lwt«i@Ö­8á$™¦qÔ u½eÌ(á°`]Iµ‹¯•Ma^¶·ËFLÝ°2Ó{WDBËM÷e¥Âx+ŸF‚Ö>ê—§_ÍÞÁó¯9ËOÍ÷æÿ?ó‹w(ÿ·žaÇã™K Aˆù°˜O±erÞdëì )´ åZ:\œ•$6ÈáxÔùrüƒ>zÎôwò(Õ;0¨Ñ³¡×2dߪ– •;@¨³£9…ƒÀ§•žnP Ñ) q÷9+ ´zŽ
+L¸NQ4L˜nLbƒlС@£Ç&Ýf>ß6@!
+bŒõM[µ£2sW÷Ëý⣙wÄÝ]Ózp¾½¾V
+ê”ÖÉ0W¥àŠtÄÏ6¿?<íÍÈîþªàˆ+Ñ_ÒœÚ
+äõìñ8òÖm€Y<jÅC %`±jÝÂÜyR[K&‰V.Q óÞl€,‰èœ&i%bQ^²ØAV¹ ­ \O–Ð¥%¤Ú
+I 1JÝP¸ 黳·áŹz‘4d%”z¯²£ië€c–9–"»Yù²"æÅzA²ƒQº‡áèØ‘ õpŒ_æUGš‰%”­f
+BÙj¬ŠL¬Æþ~ Ü ÞR¬8Ý|]UÛúþ¹‡å,¥pvFúKÝ÷Á,<ÐÅròJ/¶Èåo¤"+O6 DRßÑW5üøîýÃþ:Úýr÷îö~sÿ6¿xñíåÙÅ7w×åÿ«wßœûôWJ‚LIðÑQŽ:ˆØâD¢;!ü0óœXðÕëßÎ>}ÓBw-ôY Ö%OÒu1ý1dÄM = ñ¡µþzèÅôÕPTßéz²~n!Ù:Ô‰ê‡ê¸Ý1¢ãÐGCéøkH}'i×ÿ‡Ô×ç-t?äÐ~ÈÔ/ZèÏ!õ»ú²uè¿jê'§FUþk¦~ÜBoOíŽ Ý>סNpžEÇù”œ,œ6ŠòD7 X'¡‰¢k=Kö°Š#^ ÁèiÐçüL^ kÐhC$3BIh–ëa¼4š®(qAˆž¹Azi‰Ä-µ ÒÂãx( ;
+:a°­¶AÆ"·K †ñÏ
+é§Ù/!Q@
+°20˜¯k‰¥Q΃ª\Ò!ÀÓYaô6ÈõÁ)E´èi±~ÀqjÎ;.Vâ:–[íÈ-º‘ÜêÅ%œ”ˆÑK¶ÕX0”m<XÆOIˆ›Ÿ\_‹™œ
+3óòð|XX§âã^L ižs”Á „vžˆ¦¡RMJ8+)«ŸÁ«¼å×$‚´#P¼ Ö—Þ¥šùP´ðRd>i¨½ð“ ¨ Ñ©¢]°šQG*.ê‡=:OðÄ䯧ÁR@:FZÝ$aëè|Æ•iæIKé«!qU‰h€€§îl<F>‚1¤Â UáwÌâ±±YCôy®ã»°û1)ò‚M¡²èdñ.XWæ ? Ó°'U\êƒt ‚oŠû†‘£¾)˜×Ð Laƨ<ì5¸åYù€xÖ“Jo
+e6ÀÊ3Í=›Í¤ŒÚ¡b™œ5–vYÁh;¢pìóò´,¾Ø‘I/*R¶Ê®NîTp^,Š—bõF
+÷`Í ŠwÁvèYþz4’×ʼ̓RÞó‰‹ }'K÷°öi×:³÷‚ø¶^pGÀ¸Uf­XÑtA"Έaq#2<c™ÃÊnY<’’’™¢u;]ó¬ÔHµ³â´¶šÛJˆAQгø
+H‰ÜV{|MWþöÙçÞÜM$¤‘œÜH„ j¨”ÐP´Ú (ŠD$‚„ $õ~Õ«f”JJ=2hŽRñ~F=Æ㊡¥¦Á`F¦Ó{Ï|÷D=iÿŸ³~;9{ïµ×ZßZß^çB
+PD[ÝDœaë
+·J™i6üzÑol$#žø}Ê°Pƒc/û}eåqåCËÓ ¿ ¿¨Œuööûö{ö»ö;ö[ökö+öËösöãö£Ï²‘ÃÉÇã¿ÇHËVË–u–Õ–U–<²s ëŽâ}Ú„êVŒÊá)¼o±ð«dßò[§Ù+äÿ›ÑN„dµ9 >¦Cvµ5|XßK7ÿ£õ›Ü‡ã?ÇøÕ,ûͼA¯äy^Ë«˜)±æXZ   QKëàöJÍçPÆ¢'ÿnáÈ=œ+¢sàÃS›D3Uˆb5˜YuÃ8 ¯Ç6žK@‰pá|ó^<óHœeZöza7ª²~9Ø% ¦sß¡ïÓ÷ñNJޡ̆© ¬Iu/ˆPÑKÀµHF“…m"TÍu„é*,ÔHC±«gë‡Ù!jÑw1³o㉶Š§,ÔÃy¦5-÷€ÓÚ Ì%«rÈ—|ê•P¾ÃD™bvì¡Ï`ÔCô¥f*Ù¶‘U.K•eÊ ™¢©;ÔÇ })£ðAur O¥dëC<"†.¢È2OÓ;èúT}š¾TÿU·Àž´Þ ý0€LžËNR ?á/‰CÒ[ž–ÿRýÔ$u´ýšÃSï­OcF}Ј±G£-ãïÌèGPrÉó³†?wž~_Ì»)ûOÖµžK"ÒVO5W͵ÿÇ‘¯§°ƒyó|?ÞdZÉdf1Š,g]Kœy{
+ˆæ,nEW±[ £¿³fÊÍòü¯ê­›ï9Â1Žõ`½«Þ]/4î­ÊCÐƈ¯ bÑÛ¸Ãy{Fò®dó®Î3ª¾œ7ûszqÊvæuvÑßÞÐÓ¼3·xˆÈM¼BTîì%Þ"€ Í(mEŒèM,FQæPVSò‰úkqI\eâ¡Áï
+œ›Ùöe±ô<»ƒa¨h,¢ˆ©¿`ýEªg©h·8+®ÏDT¦¨ŠIq¡(‰ë5%†¨&)“•)Dµ–²QÙFÙ£ìWŽQlJ¹¬&)‘r”Ì–“ä2Y(wʽòº|¨ÎRW:Ù«Þ6Á4Þ´ÛTfö4/wéî’鲌ù!Ï÷ë ü%*È=ÞY}º0±/¢Î ؤ"}E+Äá²(E„x$2·ˆv.™à‡l“éèÆNÙuØ«g°Ö)X)«È:Ò•¹š/á¥ØÙE ›ðà÷¢ ¶:ú±—,r©ÉýwÄü[žÁ™I¦f_Î…Ìê~JØkæâ¾hÝ´idDãðF ÃÔ­\7Ȩø×ñ«íëSëUïš5¼<«{¸¿RÍ­jW‹‹Ùù›] aû qZAH\Ô±c#ç<(ž ñÏ,Äh\êð¼Ng¨iÏkFS3éÍè
+Íè'šÂC‹BT£†Zû ­àxLV$útý€ïób‚zkeÆ{ã] 1&Õ8 ä ­}­ä­@Äií :ŒIþßÕÛÔuÆϹOÛ‰ë·;Ž';M‚qB2+¹%qR; ,fq!Ìy©‘4 AGÚµi·¸ª¯+]%þàÑõ:¼:â1u‚Ië6ª¦ˆ­Sƒ¦ Òi`³ïÜ$¨ü3ëžãïœïÜû}ç{ü¾säH²¾—ÎÓµˆ-Cºª
+¯Šñl"ÛA“žtࢼ7# þ¤?PìÛУÐ} "ÃèWÚÅV¥}|ÆQÈàcÝ=Š¶%ƒQwÏ4Š=žHG'Z[°R¦#²¼ëéåUŽ®/è#Fözˆ‚]=ªn°;ªA<™#˜ßÊ!3ÉÍE+®‡åÍIpC‘¬ ®Þ©¢˜4ýø ‹xäîÑ«4;ÅD_«+mAr×ΓQÉ}šSH Æy¦  D¾þ›ÄОJ©Ë Z/Ä(8_ñ x@“Q¡|+H7´É+`ülµ ,“”…FbbÖ'ˆù‹³_==Ó·0Ãù„ˆ$žðiÅïW*+I ð-à4ЬI‡ª;”qLð(`2´¦^J4VƒÉ½^â¿ÉŒ„úa L¬í™{P¿s
+IÕþ„B% çâ"Ç'œ‰EΓד"ê)õRcU4åOžÁfŽ 7*ØöØCóü6È%Yn=mrRîË<žè=‚(§;:ä±HrQóÌã‹“NEÚ›ÆUˆ"Ÿ,#ˆGi–É`ÇâøO°ƒ
+OçÚIKån§rMøR®‰Z‡—ÐÄóŽÇ·™?À YDÕ¨}*íƒö£¥xiYy`y ª‹8Z뵛˶[¶[æ}P~ÝvÝnb ´Éb°˜Ê4e†2SÅÒ!dZQqÐvÚrÙpÙôG“`×U”a†jl/ƒêÜn$Ô­héžYOç©L¤W™bCð¥136GÍn}ƒ¢×»y$+îpeÓiþ[ÂÍYxþô©ÑÔP= ÝŽêÞY‡Ñdo 5Ï6‡›Étõâ”P[ƒzq¯·‚KËËCËëêÕ>¸ *ÇÛÌMT¨"ÈQ¼Íî+/¯à8«Åf .£ëêBËËÅRÒ—b‡o¿ûûWå-¶ó»ÇÏËo»]Qj¡~£{¡ãpnõƒ-ÃCÝ»7¼¶­·¯cyÃó‰×^ýóÄþºáÀöe?ý8%oýõ๹Æ'Ÿ{^ÉýmdlôÚÉ{»—¿½~ãñ$ÝØ×ÞÖµ±©²¹#w‹Ù ·=+åÓÇÈ‚1d]_—ôàLÑ Š±t’îRz¯Ã_¸J˜îÂ^£2¯aûŒÿ‡ÂH,bf÷£á£ô{œÿ?'øoÃ÷§!æSàW-Úpú]öKÑ<*队°,æ8Vƒ3ø§µ(Nÿçñ!Ä!ÿ”¡ð¡³ 4QÝ9ü&¾9Ÿu½÷!Ýf„°švÍÄàÙ,±t0ä5²!ŸÑkÆ“¹íÔ<™b^OŒOÿ÷Ãhñ„Ø× …½-Êå®)Š8#®ng·k;ètmunué54.Ôèµ.gÐîØbWGOºQ¾;ƒ-’ÀÝü®[bÃïÁ7£%çð«8¹¨Ø
+>¡_°ªðy˜@'èTÛ b¥6òÿè¤'RrT)¸ µIKiDÃåÒGÏâ­è”î}QxßT Ññ|ž&Ï´¿
+ÖÛhª‡d¦p@˜¢÷`ÔÏ*%%…æ|€I_+5šâsÔ{ùß߬X*fdIP/uB ƒÝ¡7ìùI¬w$IÈ@Äôz¡ÀÐ
+Ù,˜NpRl5¤”68©kHpSHR3¡¤ u’Ó2ÐL¦iJú
+ÿ*ŒsãeBë&Ik'sÃTO-¬K{«D'Î΀:‚Uxp
+ï‘™X7PyNo?6ÉsaB³ÊIÑs"XKU•Û¹sÂq{%Á9F¶Œ5±8Uáõy1*NÄZggZ¡ÓUö´¢7­¡c
+¨#­€uò­Ðñ·‚Z]TQiEœ"TVÃáÈ$£²9™‚X
+&òD›™‡©™(ì¦É„ˆ¥'þxµWHcù2bHBÎÌ[EIÍé´¤î
+ñEèw
+ŸûÀ±eƒ`è“oIn߲؂¡®Þ£«Þ]^ë¦8v†Ã1ߟhZ'7þîÜêÕ³¿»c霗?ô{evÖî¶ú›ŽDÝJb›Rl¨¯x‹a£7ùgh~ìkÍëšPü‚—îü¸±"€õrÕüˆóÑŒ`´Œ´1G뀳ä0†Ù²×m"!Ù">xôÓ-…"Iuí2]a[™×™qNe:tá ‹L”Q­[)›T½zÏŠ:INM÷ž8^|·x† xìÄý¯ýòê™Ý¯V6Á$ÄQ%Ó‰ÎÿËcš«zÊ“èͳڛ»Ás²üŒpdž©aÃQרáŒëœùŠY— ‹$n1qéÌ1:KŽdlc¦Î³wÖ#vrcº†FÑQ¾}
+R¡´·&Ô¡KÕ ’Ѥ%ìnÊl¦T8œƒµµBdÐ
+pó:„Ínt¶B·Þ© 6iÍ>Õ—È7Ôd 6ä…
+`XQ
+e"›p,þþ¢âÅâ´ßûGÁÅŽxñLÁ‰±;êÚáiQ€Þ„KoÏg~{p1<å:Ðÿ2z"Ä5ØÁV¹ÂOqSÎ
+Aà0H"`
+§¢o%¯G¯%ÇcÿŒ1L¬‘ã¼GvT‘†<á.÷T3ƒ|uΡóPñ>Þ@äB¦p§ÞÐéÙ¯_[QaòÂmÞ½'¼g½„7Û
+ }„jlÕ%aDT¯÷N›Ed¦ÓqUçÉ™Îp•Óåþ?ç÷&Ò,—J'Ó5åUi6ž'\U¦«Xfj¿ú¯h೤5y©ù]IiÊó©.ƒbAÍ€x2Î[K«ÞíºTê’°m@~ia•G/°¡F_rýle_´¡áæÕ%ýkØáÀü–7w=¯-Þ1oç[»V®ñÖŸ9œ³Uëî;ßÜ` ú[¾ýõ#‡«Ò«¾}û…á˹•Íx
+ÖAîÝóñe´_AµÚˆœ-^‘=ÝúnCŸ¾ÏpÅy%ð¾óýÀGÎ̉ì~'˜Qøžl­´÷•ét
+•»­v›¡yn•Ù!#lz„wRÅö½²
+ÕɶË>ŒfUšúÔJØË ¦~6ëD¨ìª¤Ÿ)©ÌĤ¢h«Ñ3ÅsfÎÂᔕ6µBŽþWQ¢DR)äI‘²åd#‰Oeû4îÏ¥(ï³ô„”j$°Sž5dâ[‹gáYrD7ÂŒGø3–3Ò«q/¶×€¥isŽÒ±¹ÿ²_õ±QW|f¿÷ö|¾/Ÿï°ñ­}`0ÆCøpÌÕ|;à »¹Ç9̱áÎ&|A m i D7 
+8GÑáÔÅY9ÆrÈ2Dbóžéñ_Žléñ³ul­åLf+£¢ìç”áßãìç„ »Wà*²ÊÌ;HdÖ²pàU²‹Û%½ªëÉFn£´^d^§‚‘H¤Öôû‡(Уù-¤ž³ÆnúÆ€qÎø”ÍŒúzò‹äuzȨ¿1ßõüénל珞ý•h
+a¿Ã±ÖjVÿªýzïà ñ‚Hä&¸UÍöì "¿o,$`éàààïE‚òG¿ûŠ„‹)Ð~¨
+“›zJÍÈ™|SeuMåâÊŠùsŽëáȪWÄ£M-/ëùú¼–¦5Ñ„^ÙÜP¤/¬o-ÒgGEzÉÔi…¥%ÅEú7ššôE±Æ•­ }Q4¯¾T¤›7{z~ãÚ¨¾¸¥©­5ÖÒœ(´:zÒ[óÛb/Õë‹ë›æK/Çc­Ñø¢hc[S}üÉu|:]¯ÈŸ7ÝÔü?7¬‰Æ †>±hÊ4«%k8þQCKxL%ôz=mŒ%@ø/î«5*ªë
+çÌÀ\塽™í¢]!MŠ¦•g(0­š ‘k|€0 (0„”H|4£iì4XMmmìÃ>îÜiÍâÚ¤íJÛWL_bWÓÆ&iº’´«k5tŸ;c„È`û«3뜽ÏÙßÙûœ}ÏÞw__­£­µºÖ×TݺÕ¨›Ü9âH%¾ê¶öV_pEÀ±¼¾ºÙï{Ï­„A3eõV4¡ïí¨Ajiä@õÍ·Tºú° ”ù}D+ˆ
+¹À¶R?•µOÐüb,¡ÿbâŠiÞOó´“m4žO­”øFÚ•ÏÔQFºj°¸U¤·ÍäܦLp·QfZ‚[ðqâ™3E´¶‘è]¦æzZ4Gb…°ßA}­‰¼º“EÈ·§+»';i' ĉ3Ý2aGÔÖTü#Vû ßhúv*+ï[»Ôôï|òçÒq>ÿo4ÞmÎÞ:"Ÿ´Ž×yEcÞ¤Ç#¿KLÛb'­¦%1Ž#…çÄ·’´–FMæ¶Ò\
+1 YļLLM¦lð­úi»ã‡øaýCʱ¢~µ!jù!XY¯>’£ÍdÝØbÚ›0¸‡…x]M™uB5g:Hç»,¼¤j ˆ®ÐG\JQ:û AÀK; QlȬ”¸ÓK%ººR1˜SX1˜¢«å4mKÆ?h‰L³‰#°¸ò ¹Òºo_¥¯ŒT)/ÆÄF¢~ùÙƒq¥*úH@‰PÓNÆÍâ EeË zêÂMr-|ðTB¶›…,=уòNõ¤9Þ!æu%2,Ïéýrýudþ|ŽãûIY­R2x®kF•\­¾/¯Wß-ïQ ±rMœ¤¤< 3)ý4ÐÓ
+ @ÍÂ{ôµ
+9¹“·šOq{œ²ê¡}Ê)ö&B<—‚_f—¢B§ÁþŠRÙ®÷DÛãîæë¡Ze•‘€GUyH5,ïF/ÈÝñ <'÷«ENó
+µ[©R[Imï;‘¼FIQR¿ݵÂ>o ?j ï·…÷ÚÂÛlaŸ-\e o°…×ÙÂkm9Ò<É!}Dš-Ý(Ù%Yš%eIÒ )MJ•$)Y²J\‚dçLâ”áwS_KQÀ°Mô,ØôjC5ðnvhïT8 –zç:-ÉYÌ´,/¼«‹í1í}Hî(´fy+*c˜5vÑu³x̒ͯÞä A’­¢·š|=¿2–e.)qOÒUgÕÜköq<ó®ê<C¯^1
+{Àì]'lÊ%›´™oÂ& l¾d '
+Hä¨J€ˆzÔÜ;?rU¾Ñ”ÇÒ¬PMjšõÌöI1Aúá}O(¸1¸‘Mòä&ÿ'}Ź¹1 ³‚HW¯ÇçôT9=>jUZoG½]Û½Ùሡ‹‘C³Ì¯Ú\S/hµÏ`NŸ[ërº‘áÞkåZ¯;ÝôzVWFz]>·>ìö8«ÝjÔ?Ð?:Á\Ïs‘þI” eý–tñ¨û…­QakTØò»ü¦-æi¨(¦›[‘P¬._§Q>-5kyeUö\µXÎhY¦Ñ vÇ\ûÎìA+Ø㘖«jiÎbm:5!Ê+Ê+"Š/!šAÓé ‘}çs³Ùã QMgRèN|$Á6r|û
+H‰ÜVyxgÿ÷ý¾ïÞ"Bˆ%Ò¸."¤HŒ¨eT,¥è¤‰$b‹DDšµTj«¥¤‰RBì¤Z”R[(b‰”(b»b¤„AÓ¤ó¸ùæÜ/™¢&Óÿç=ÏûÜûngýs>
+³’£4e µ¿ÔVÖB˜´¨BhšP¥ZI*üDâ…áïÙÓ‚å‘мi|Mãh…U*?–eSv.ßqçÿ#+Oíù5rÜxyð¡TTÍd~­Rå*NU«Uw©QÓµVí:uÝ깿îQßÒÀÚ°QcÏ&^M›y¿Ñ¼EKßViݦm»ö:vêì×åÍ®oùwëÞã퀞½z÷ùkßwúö0pлƒ‡¼7tØð÷ÿôApF„†…Œ5zÌØq‘㣢'ÄLŒ÷a|ÂäÄ)S?š6}ÆÌg%}2{ÎÜyó?]°pÑâÏ–,Mþ<%uÙËW|¹rUÚê5ék×­ß°qÓæ-[3¾Úöõ7Ûwìüv×îïöìý~ßþe>òÃÑcdzNœ<u:ûLÎgÏ!÷üO.^Ê»|åêµë¶ùP„[Ú]Ù÷•ð¶a7tò¥Bñ´Dd‰“âºÌ´Ô´¸Y<,V‹§ÅÇÒÑ’ÑÀÚÀÓ*¬šÕÙZÝêju³zX½­=­ÁÖðƧ ]w`
+¯º¿ó
+c^¤ëú­²pèëõ }†ÞM¯ ”Vž]ÍO+;ÉOÊOáYýƘ ¶[ºm¾mŽm#`‹µM°}`kió¹RTíìžCô 1~Z>¡”þˆ€ÿŒG¯ìd`-f! ‘‚[ø 1+±ëð æác,Å}<À¤b®ãX…-(ÂC攎¯pYìéÅbÖç4Âq§ð#²q9(ÀHäâ,ÎákDà>ÃœÇO…Û(Ä\ŒÁhŒE$Æa<û8
+Ø¢LÄ$Ä"âgÄc2ˆ©˜‚=XƒiÙé˜;¸‹ïq ù¸‚«\9n 6Žøw8Šo± Ç0G0?`=#ã"Ûg £-céBt(²Št–ÕeMYOzÉÒW¶—þ²ÇK¨ijà¦ûŸ!§dDÉØ’I%“K¦”L3PôªTˆ$HÍ[ƒå6”Í¥ÏïrŸ#ÌËÀØ«rÿ€²’à’1%Ñ†Ü ¹¨uö'öÇöGö"ûC{¡ý¦ýºýšý¢ýŒýô‹häéÀã™?C¤y‡y«y£y­9ݼ†ÑÇqÏd¶÷yjT6+äBµˆ³˜Ü+87ÿ¯×\ËèÿÑ Õš7êª'И§§Òu9¾WyÞqü–NÒ ù¥yÜ
+òô'j>œ„M¿£^4îÝ¿¡¶ÆÝT-ï?zCì3Ÿ»·4PhLZ s­Ï4ZÉ-»ÇºÁ˜ÿ}$³ÞKØ£9lõxök2†òÁ>:Œ\ªƒÍ4…ÆÖ&ói¨£òÍbìÇßÙ!“7qˆ_Œæ÷Œ½8LçDüøv¸¨ ~9„O“±§(.¼?…åy"J颧ê·Q-91Ì'
+Öú&’e€ðU¼¸c¼ÇÝ ƒôÕ¨‚X[LƒÍ]Ež‰Sðopa d},2ª,Gí×7+ÿæ»z`›¸Îø{ïÎwþï³ïü'qç\ì&\þC!WbBÄ
+Œ’ÖI6-¬PD´¡­HåÐUšX5èT6Mm§Ñ± ÐÖ´2:`ý³µÛ@Û´‰âmš
+ën+cvÃçŸäZ‰ ½mºÈÿ•Tã_âs‘cì v'`3F梻¦2Áþÿ”<Æ>Š¯Ú½Â¬‡îN3±  ÌÏÈ.æ
+Ár"°%.ë8-'ôö][2‰t~oÜfmSÚ¬µ5hÜjÓ–Þ®lÇíK±aöÄâq‚̈J_©Äz‡§!èL8Ñ»I_½&™ˆC¡TmŽÛú•>)Ët—j¸ 6ã6:צóÆmä­44&לËÌ
+¨/­Ú7)›z»“:Ó›¢÷p«úr%®/ß3¨­Éâ7Ö%uK[£uÉÓhåäÈxÇH<žÏ “ÈdFg»×Öt®M† %qP¦®M±;ÔÃíéM ˜Ê€’ 3ém²nQ–)[2ÛÒ
+ i‡&ËdÚ¹=“Îôf'GúYP2ã™í‰ôtJÙÉscA];˜Ú‚¡¨zÔ¨l F`§=ÛNºAZ¢y9þ‚‰‰ý€AVÎ4Êðì(FY²ìmB˜c–ó§ñ*P…»1!ËÓÔ
+ßô=¿!ê¹Ã!wh„Aù‚
+Ètñ^Ë{t†í*ÃvÁÃ3„^ÓÊ%R Ïëuð$2½*a ~[”$$ YÜî,þR³[\‚Ð…`I$‹÷kåq‹"rùm¼‹s ÝÈZå>:wÌd“
+ô#-85@Ìáõ9ñú,²¸Îà/‘‰€œÍíñt!'
+ù\L0p|¼çq€²ÍaÈ^Ñ0kdÖÜm„'72ñ4Ÿp¸©ráŠ.[Qø|âÈ©ÜGê}¶ðRá>°-‹™|ÏuÌ.]ºïÒO?üä8°kÃ¥BÞU=|øÂeÏÙo?ªîžfGÈ
+FÇ}ó€>/ÄbôMÌþ"Vš£ZƒñPŽçC|Çâ!éÐÆg#ªîõ—„’{+x¡Tø¨+<mÚÜËnzÄßm-)9qBë¿w„¢»¯Ðm(V t~¬)’¹Ò¼ÀÌy³ÙR r¥æ gÁÎj(S”8.h¦lKKmsݼ—óY9kUdÌfÃYò™æ@G™ÃW]8.K~£9-ÊgG5ç窭†dA æO ­ÅTO«­­T¾(Fuª‰²H1C»œ†z¡ñàDSóÃ
+FBx¶„½¿ìÞŸ‡iÅpáãçûwâË cÉ%®$ îïWšê[n(öBEáÐý_ì;¾Õ´Ÿ*
+Ú¿¯ÚØÎÿQ´›™ЋrômŽTÆ•sær–c±{®Ùe–•tÐyQ”¦èƒ-lyyÂ@ÌÑò…ËÊ€B¤›Ò•ŒYyÁÝ]²o­’DŒÑü¯¾µ¢Õ‚zµR–P’P}Šñ]FëT'Œz°›öѨY8_¬UÈb˜hS¥ÑN<¨|Hl*¶ÃÜĪ »VYžÅÏ­ê_üîØ®ÖØ(®3zïÝyÏìÎììz_³Æk¯M öâ5¶h-v%L)ŽL“'¶A¬(±Ò` A´ ŠJ’PMK
+‚³¼JUÅ´¨NCyÕm%'„­ú0%ìÐïά»ÍŸõ{V¾ç|ßwÎù¼æ/vsÑè²»o,bÿÎ1]6»Û¦-Ï¥Òµ¤xêTóžùµù}è`\Gå‘Œv%5 ¢ã!IìÀZ)ò>,B
+ùB]Óº]«fE†ñž/Ètžr*ÅÀ/®ýßùm °5˜ Ìap¸rŒ½`²7EüB˜ R)#ì5Œ°³¢"­ôF£•LMMþ‚‡9»·äiÓÞwáî#.â‚çþÉ“‘£ú4Ž!•Â§Ž&#Ÿ‡Â‚FM4ZQa€*lTF¡ÿŒä#¿RwRµ†?ú #)ÔÖÑ«3ó½YP1»4‰Ñ¬f…lgg“–íW*Ú£ðhº:'” ÕXÉÄ*ï@v¿òñôËñŽrz,/û¦Þf\u÷áÇü·×lœ×®ÿx§Ç<´ÃY>yAvoÛàv×Ü}æÙ6(yÚ<„/®z¤ôÉ­°cõc±ö\Wšü¥¹´ú¡¯ï™ËáI™¢¹¼0å ŒÊ$‹E—è ¸Fð¨&ǃ:2!Î`ıbi-–Ot©jJ÷xuÝÃF¾8!Z™rŽs ÀV‘Ïs¯uº¬{tà\ÿ¦™·>kGºìÿð8¼ñ~¨ì*pù Ã_ nÎf³&³&ÿ´yn¡MÔÕ{…Á„§NÍݱ‡¹?yà·À‹ þV›Ôn±_ÉDßÅñÊ.$­sdHê}uØ<ð3jù·il£Ñ&K‹ËŽ©u‘†*ët[‡@ª‰¾ýÞƒ?0Wünï«–ì¹±*xî
+ŠKRÝΠèž„ýNE*¹œt;E§îTô¯€Ò+Î7X!’^I÷«ç‡U‚U)š8Ìó¼))…DÞχ¤¡¿âô¿¡#c2Ѿ;:B³£ð  (—ªò,  ¬ ¦Çìű¾ªoläã|¹ƒ³WGòÖâ‡{^R6¿æµjhzΗ{Iš9sVB©xáÕMd{ÏàôÛ=¹EuÑx}”"ga{¬ffOüí(«(Ô d1¢Ý B›"Ñåv§$Ù+Ir L!?4ªÿ'd!ÖæXWï[à[ã{ÎwÄÇú|HV:\¿ßÅ…ª‚¼0·R¸J–üH«EgÐrH(ã(KêË,óÂ"üÅ/…¥Ýùp ´Ì·V‡ÎÖ‘x§m&6=¹ê¦DÞ©­ ³`Ì.pš¶áåqŽbÿ˜›½´9Yî#¨³.žL¶Ò<aاë5oóóæGù‰ÛwóÑè)Ü°hðƒž4n¿’ën«í"¿JßûgIxê4"âÝ¿¨Ïþñþ(+Ehý)ä“ õäBÒ#ë¢à(b:œW$ XîÕN’+4™3éLBÖÔ˜c9E^Y¸ˆ\Û‰a CôfŸï
+¸*Zýk­kL{äìï„<(I=ÚR)¶h-"ÀöËf¢èi.Bžù ^{Ä$­{I}ô&q'Ç?Öê°@k@ù6½©m7ûÎpÎuäŽÚ:bH.RÉaÏÛ&çAZ%¹~Z uœÂ13Œtc7ÊÍ5hvtA¹QOšˆrGÛ=Q®-C¹^ÖöžYÁþ9ì?Lj
+Ç`4Ï“›c8vfQïIs›È1kEgžð›È1ö‰$s
+ÓX†]ŽÅ”„
+ƽŒEžÅY.IÆ_4IÎ’:+›¤ïé§,ÒpÒݤJ£^O"?`Ô‹ý7–¼†í‡d¬Êx‘ñ bþIl•¶*›Ó´m i}6ÔŸ4ÆÙ&}åd¥-sPúŠiœ÷?ò¬Çz‘ ^`M÷˜ž¡¯0Õ3Âìh.5¿rLrtq¾åêåÚ“óilmì¾ÿ?Wþ¨;üJàD! Þ÷× }yw?§ñ¥F³µ¢s7}8ê{‹ûm¬Á96Öq·±$¼ncqx×Æ®²ÕÆrÝÏ#˜¯8fU ĈÎ6æWHdØX'îmcƒ¸¯Mâ6vßnc'-–V ÃA;ãD™ tåÿ:‰ªÿ!Ëþ
+ËyÍâM ¤hÏÚ˜ò¼s#XG_m­9W;fcôd;`é=mìDH®p,õ$èml ›¾SávìOÑÙØ@ÃT8NžŽÑÛÆ2ŒÛ–ÿg ¯©Ç*œ¬úÙXö/U8EÚol·1m6ö(ÜYõŸ±1ûM]bWªÔcv·1õ˜yÇ)ûÍÑ6–ýʆ8e§9×Æ´Ó|¶xDyqYñÀ’!+¬·;Çè«òVû§[Ö0u7h×VdY¥žP–•Û×YP—e ¨®¶Fúª&‡‚ÖHoИæ­Ì²Ôüì^Jɨ™S¼V™¿:òùkƒ™›U®ðUz¬"ÏŒâ§ÚWq¾mE:
+­Ã
+•iß+÷‚Ôoådå»#ƒë­h¹å Z+à­òCÞ€·Ò
+<•ÞOà>Ë?éâ{•fy=¡pÀá·MöÔVyÏyéJ
+£¼vùyåÊŽ m¿œ™…|Ξ™×ë’­â<ŸÚ™\%@UªRºå-â
+ñ#ôÃ-¸·¡?3~ ÷:C¸‹¡ŒãbÜNï–àŒÀ¼—îbö—ñù13o4ÆànŒÅ8ŒÇ܃‰ø ÷ûSz¢R²¼1|¸—¹"s¡–žžBOTf…éÇéôÒLÌÂÏðsÌfô
+."hR[j!q
+ •(Û'õEðAfg‰Ì.ÅâgH4>ùlC´Ä&‰Ͻ³|pÓ¹çžóÿsïܹw»#:¾èÁQ¾˜ƒÿ#“â^]³7oM5‹ž¥ÃØ+ƒ?ÜÔ1݃ßÝÔú'0
+Å÷N¬w7Âá.agû]XùbÎøåœê+d,¸…N ³¥õ”zrƉ•hW¸©•zó\XNÒ¥Ëñn!fx/ö8,#í0‰‘¥ À5Ü›ÄÞaÜÇîÇ2ïÌáîù§^8g|Å¡T|]œÀ3s”Òî÷üG”œ¥ñKh£Uˆ^4N=οM=ª:+É/A9€76Y>r’Ã@w5w Ö¹ü¤¨ðtíéÑ»ùQÓÏ;ˆáSõü !cÝ×'q!Dü5ô1ý弘­ÛÂwówE‰CŽ°”3˜Ú›jÑ7 ìIw%ïÉ‹|WïÄa?sñž_çy´îÉ:¼Å¿\+€ë~͵¡ùÕµZõs0M,8Ž§™Ã•œõs#b?¸™Ô‡]k>š!×ÒÑ úfHò`·kЛ+`±êæÈÅÞI¬’ïp»sØ^,³ÍÍô"ºÕ¯ÖçfŽé“°…ô‹{„‡ý:¹V/Îa &ßK¦0y±L.ÀBbЉ¨¦ÿ)òOV­~«|ƒS¸€y_Y†Zùç<k…ô^áõ^¿'Và]Cš·2ÒÁ3ú›¢ðˆìÀÇú
+kƒµ…'$]‰áÙEiIó’f!áNR†EI\®-À‰¢Äp5‚Ûn ´¤³¨Í(ÐC$L%¬8.å&ßïöýlÊô°×ItÅ“Î}B;ŒìaŸÝáûݾŸM·{Ø“,qh¢Ï£G}NÔêr"qG;Ñ×å,@o=Äöü-À
+H‰t• P”GÇß›oºé#D¾ï›aÄÛ¨b<ñ6!F Æ3*È©€€‚âGðD%ZºV*‡…qk׸ÙcÖÍF£I­ÁDãÁ„A`ĸñ*:Àl3¢)+kõ¾×ïß]}üªû5
+
+ú(è‘ÚA TǨÔ©êtu¦:KÍV¿VO¨%êUõ¶Z¯6k“µ©Z”–®åiµ
+íºŽº¢sôŽznÖCôžz½¿>P¬ÖÃõ}‡¾Kÿ@߯Òÿ©×èý¦Ù`ö0·3û™ÌAæžæñæ(sœE±0‹¿%ÄÒÓ2Ô’mÙhÙl)
+¶ÿhÝcý»õkëaë·Ö£ÖãÖ¬'­g­EÖÚïv!Óºßo06D7¬jp9NÕÙÛêâæáí ov¹š\O“ä¡A¾!B¬ÌR2Ü<ÖJyÊ^å‚rÇÆ\ãc¶íeGØvǽ‡Bå« ú«š:^hå1G]­V ÔrµB½§>ÐÀÍ#MËÐòÝ<@ò`ºÐý$M·êÝÝ<^Ñé#$H}››Çgn¶VÜìcî`îôŒGl+àVžñÈ·îwóøFò8&yXÏ<ÇÃÐåæ’Gg?ç@É#Ì9Ê9¶•‡<$®û®ëOÏžá¤á?†ï›E³<W”Xã•ÆÝòô7n¨/­¿Ð¢VoqT8®:.;.9Ê¥ŽÇEG±£ÈQà8+í”ã$ÀÍÇ‘›ÁîÞ5Õ×êÒ-7ªrê&µøÚ£u—j×Ê[\×½.Sêmª¼ª<íí÷ìwí§í§ìµÚÓ5Ø2jœ5U5ßÛ^®<PùEåÇ•Û+ó*³+Ã
+VÊe Ü„[ð-‘!GHà„FôD/4ah‚f™!}Ð\ø¶ÅvØ; vDì„Ø»Àxˆ„*j¨£-ŒVìŠ!Ø »c쉽 Ê°7öÁ¾ø2öÃþ8
+‘Mƒh°XMCi;EÃÅJQ$V±Ób¹8HaðWA#Å{â‚(¦Q4Z\¤14–ƉQˆi˜.N‰Ó4ž^'Ùv–£7(œÞ¤ ô.ÄEì<M¤š$ÖŠub©X/JèmŠ¤ÉôMDoÑGld…¬HfÑbv‘•°RVÆÊÙ%ö»Ì®°«ì«`6ö3«dvVÅ®³jVÃj™ƒÝ`uò%ú…Ýbÿe¿ŠM¢”ÝfwØ]výÆêÙ}ö@äŠÍì!k`ØcæÄ Q&–±FÖÄš™‹Gnà
+7rÆ9÷·™¸'÷â&Þ†{sîË_âmy;Þžwà~¼#÷çx
+$UÄ‹š&‰¯h:Íå4“Þ¥YD³EŒ¸+î‰K"V$ÒŠ¢hš+æ‰ùC±'þ,²(ž(Q$‰dñP¤Èü2æ‹<ñ>%Q2¥ÐJ D&¥Q:-©"M¤Ó"Ê Lñ- Å"‘AKÄ.Ê¢¥´Œ4Z.‹%´‚²i%­¢Õ´†Þ£ZKëh=m ´‰ri3)¢‘ŒÄ„S<‰“‡g†g&†ñ^Ⲹ"šD3!„‹þÇq]xwy&Q
+ZM«j9-/Ë1s0ó0 °‹°K°¿a–cV—&ÑdšBSišœ¦é4ƒ~¥™4‹fÓ9+縩œçfÜœ[pK¹ åöbö{§â á0Žà
+®Ê=\ÃuÜÀMÜÂm”àîâîãâë=½¯ô¡>ÒÇx‚§x†çx—x…×xƒ·>ŽùZE«Éü)€õQ0…P(…Q8EÐZKëh=m IQM1KqN8ÅS%Rm¢dJ¡TJ£tÊ Lʢʹ…²i+msü)‡¶S®Ly´ƒvÒ.ʧÚM{h/í£ýt€Ò!]­~ê¯H‡5Hƒ5DC5LÃ5B×èZ]§ëuƒnÔHîÀ5J£5Fc5NWú°•Oé¥ctœNÐI*¤"*¦StšÎÈgt–ÎÑyº@é]¦+t•~§ktnÐMºE·©„îÐ]ºG÷é=¤Gô˜žÐSzFÏ齤WôšÞÐ[O‘ÇÈ®ñ2Þ†5bÔÀø˜R¦´)cÊšr¦¼©`*šJ¦²©bªšj¦ºyǼkj˜÷Ìûò¹ùÀ|hjšZ¦¶©cêšz¦¾i`šFÒÁ46Mt“&kŠ¦jš¦k†fj–nÖ-š­[u›æèvÍÕ<Ý¡;u—ækîÖ=ºW÷é~= õÖ#;k¢&齫·µD%Ikj-ÓÔ4 ÐúÚ@%Hrd»Kˆ¤Jš¤K†l•m*a²E²µ¡6Ò–ÚJr%OvÈN —Y#ke¬—]’/d£dJ–DÊf­­u´®Ö“(é(´©6óøå¸6×ËœÔÆÚD µH:Ké*Ýd¼LîÒC¢%F[ké)½¤·ô‘xI¾ÒOúË
+£1c1ã1Õ—;sîÊݸ;÷àžÜ‹{sîËý¸?à<ˆóÊÃx8à‘<ÊãÃ1<–ÇñxžÀù þ’¿â¯ùþ–¿ãïùþ3ÿ…ÿÊãùïüÿƒÿÉ?ó¿øßüþ/ÿ“x2Oá©<§ó 4gò,žÍsx.Ïãù¼€ò"^ÌKx)ÿÆËx9¯à•ìË«x5û±?p q0‡p(‡q8Gð^Ëëx=oàÉQÍ1ËqÏ œÈI¼‰“9…S9Ó9ƒ39‹7óÎæ­¼sx;çrï་ó¹€wóÞËûx?àƒ|ˆó>ÊÇø8Ÿð˜¸‹¸˜Oñi]Âg¬¯]eW[?ëol  ²Á6ĆÚ0aÃm¤²Ñ6ÆÆÚxgl¢M²›l²M±©6Í¦Û ›i³¤¬”sçI©(•¤²T‘ªRMªKs›ïf ìnw¾»Àîq’»W|eåíääÙ}Nª“æì°ûYÎvg¾»Ð°X{ȶGìQ{Ì·'ìI[èì´E¶Øž²§í{Öž³çmN²ì¤Ê
+ä"ù@¦“4¤#Å^´—t•½ŒìF6cüàìt!AFB†pD` ÖbÖc6"QˆF b‡x$ IØ„d|ë.v—8Ý¥ž½¹Ì]îõÒ]éúº«ÜÕ®Ÿëïà;½‰Ÿð³+-Ý`|ï,ññrCìüˆ0S1 Ó1¿b&¾À—ø
+_ã{ÃÞ´·ìm[bïØ»öž½o؇ö‘}lŸØ§ö™}n_Ø—ö•}mßØ·â‰W¼Ä[X¬ˆ(~Á$L8QâãI)©ã”Hiy_š8¹ÒÀ+Ï+×Íw Ü]N–×3©áf{§8lðÇH ÿŸ›àÆ{2ÑMvÓ=¿q3Üuÿã¾êƒ£ª®øï¼· ,¸ù
+&!è.~íÆÅ ©Eþ°3VÛí´¢/ÑŽ§t¦Åѱ-ÊxSÝX§Æ¯–Î@¬H[¿¨µÓuŠÖVû!Ûß}û6 !Š3Nÿñî¾ûî»÷ÜûÎ=çw~ïó~æ,æCæ£æ!s?zOCæÍ=æƒæÏÌ‚ž©»@*1 ALÆLEÓ0§!Œ*T£µ˜™˜…:ÌÆX˜‹Ó1gÀFóáàLD±
+4ã<¬Ä*´à|\€ ÑŠÕ¸q$DÖàb´c-R¸ë°—¢èÂtc#.ÃåH#ƒM¸Wâ*\kEŽúëÜ&ϼæ:f0½ÌWú˜±\Ïœe+ó•mÌXt¾r#3¯ÜÄ\åff+_a¾ò5æ*:Sù³(cÝŠ¸9Ëk
+|Ö…¥—Q¼¤¸‡í!úÅ5Åû>^\Öà=ú $ËiŸUø‡Ô³oö Dïù#^O²g*ùµT†h»’s?±È'<­æªCäêðÁI¢{ýk´|ý”{Ôe7¿U¥roéf6Õ¦‹§ˆ”»Éó'–!¾y'¿MOÙïJÔˆ¡ŸYg rˆHî77o%ê´ÜÉå5þnãìÑý,²qw„ñµˆ%›±ŸòÞâN_Á[Œ‡§dîˆÍüôïö–õ+·ÐÖÏó;ÿ© #Ód¼~LadDYûœ8ºÿî[[ÇD9>¼Öª‘Õ·+øŽ¹2“X Š‰ñ6­„¨â©á›<ilóå–PΦMÃä$ÿ¢U1Îw•¥d5Œy(?\Ü#Qr&¸×OU*®Óv/ÇsÔãÐè>*ûq^Šð÷˳Ž¿ëÝãïß8Š¿áWÆc2@Íž'ë<B¦«ç h­Þƒ¿ ?Œæ3ÜWŸ¤ËÜŠÕ'uނܽŽ–¨Ëkr€oš¨ìäoÆ+;ø5Ý;NîiÆñnÓcÊC­gÞ„÷ùÝ[Þ¤6‡ŒÇÉäwÏ[½¾»C|Ëbýkmíj¿¸eÕÊóšWœ»üœeg/=«iIcClñ¢ú… ¢g:ó#öóNŸkÍ™]7kæŒÚšêªðiÓ§…¦N™œTY0 AƒÔ©ºx:Ù§fdz*ä$œ°­Bë­kR¨¶"N•½¬)ÓèK©Š˜BMJÕv¤ÑÚœQ•±ñ"ë• ¿áäu–T(ÿÎÚ\^Õw¥#Nø%kd<Ã9jN<‰XʈòßÎ!þ×æì¼
+w°?b•zÚ:Òú*ßhf'š#Ö]i5¯ü˜ÉL¤$¹¤8<NÍõâ†C³ã …ÚA„ÞP˜¡ÅŽ5óäÛ¢êcT$Ì–·š”Ô¾§¤FÉŒuTùÄWèiG›'°A2ßç$ó[hÑ|vÔ¦ÇJØ®ív¥«–±é)Rû:ÓƒS§Äøæ)ì€×Á)SÙ3Uwp‰mƒº@¼†J®4œFóUku“úêS­ýY6œíÆ‘šÑ‘Bqx`ì8­Üª)µJJ¨Ê¸šTRÂÞ¢Zs
+ýö`ð;P£' å|îÊ´2s„Möv«¹©ŽMìâ«xe{míî„WiçÙÉ^Û峖ͲvÚé'ôç{7g5L$ë$869žÞ¶T5ïIUSÓ(6íæ7-ÓMÖm±õ£ëî°Õnª;f4¢k‚ Žª»I‡oãbɾ‹´KšFÜ桱=ï9§µ?g«í=}%ìåÊø¸aúg„Þ¡8Ó›è›2ŸíÓ*÷åô6“}¶Û¿ÙÛꀷ5âÕNö%ô¥'ýØÈÙ›ÒÉ^'9úBnœ 3:~n$¢fÇôD×Mjsyj_R™£ú똰bB}⪵ۻ¡ÛóßØšKdü._`“ž¦G²‰L&Rò;Eդ莊%Žíê'EUm,yŽcà ©®t2ay»WF<}þ;uÖ;l§:Fº¥Ž2nÓ;VÉF© Nª³„‚Þr•í.°1âyŠúòÞªê¬l·9mY×msì67ëæ
+Åí=ŽvÜÁPÈÝ–ÌÚ^ä ûŸè·TÛ@F…³½²’NÖxkëJ©šÎ+´{ÚìÞ\‰,.t"ÍV¤*S–éø¸a?Έxâ^Ç™~›º…ÈH–ݦé¥@V°T¸Y‡)5Ù˜f\ëaÖ«¸¸¥#ÅÌD“[6ø"}ÀhÞëô{¹H$¢c¨¿ÐŠ>¨íéÒ³ë'hmŠÑwY=2\™±Ql/ŒLÏ:ôU]jÃ)0=Ïn•SmŸ×äÙߣۼîæ?lVÁfßÝ5ñ´i~Ë°LÝš#}µ¨Y1o¢¶ YÒ ;öAG…cª"ž¶Z2v¸Šô&#`ðWÔ0 t~)šDQVÒ¢d¦îIÕãvsV3G&ÚI7ëÃlìþü/A¾wâMR&ìpŸVI¾ªÚÑ[Ýïq›OÙÑ6TV¤$±6£¦kbVÓßö*nΊ§mÒöÓkØI»W{]ÙÙ„Çklw¡x4›ÐüG•µˆåã›uf¼RÝ#­®ôW­›3lhH0™_R‘;3)~«€ÄéCÌ]Ík®æpwƒm'·$øB>ll`Çâ[—5›úi'£¿$íùÿñ^­±QWxÎÌܹ/¯÷îz¶±WŽÝ´ µYÓÆ´({KƒbRRY¢Ôrm'`J ØDEEÀ (%rT…Å)I I‰øÑH•"J±Q+VP7”"×é#)ŠÒB€® u“Tà½î™»^³Dñ#êîÞ¹¯3ç|ß7çÌôWKu¯Â˜¼otõ·ÕWKqb¾$8ÛmwMw»ÚÚ¾†vZ¥îÙéoC k§,¬õ, >´lÖ·0SÕµ,Ãd›Â„n? CÄp38«22bHÛ´§Øn{¼|ÊçåèsÛ—°³"o¹L¡‰¶þþüY ʼ¿ÿ®~Œcê|Èg/ØS‰g
+·˜*1HÚ^^®Ã°þ‘N+((ºÁ…f¨œ2ºÃà!Ãà êN!Ü,êŠHÛš*gŒ©Œo`)v…1¹·0,c‘1®ŠQ­>K&q z'QYñ¾“H¼í$Hy²±1m Ϋ¯Ì&w+V|÷¶ã»ËóÍqù›Ó
+Âõi•’´†pYš–iÓ—Ö¸I¬]ŠÝ‡6‰EÈÂuF¢å ‡r뤌¤R†{œ$s£Y<œ¬G C0Ä||µ"VM‰%¢aÜŸRì6&îS:ßuGÝçܵð"lý÷˜{õK.Æÿú÷¯wÂ~èïÃÀ¼«î»î˜ûw¤[òL%ÏÊò¬‘2²Ön~Ì„ Zøžâ{8_¡Âw¬@Åñ¦`ëýà3gšÔgÍ´¨Îõ4)+Cj}») +=4”'nŠ4©)†0„Þi†òÔ6&‚¹_ø2 -t,ƒn÷I÷{öüøXç‚£GŽ*Ãî€ûš»×]w|ùÑ£¡ßàéó)ôÛ$ í:-­ Ò”tEºäèŽçŒ`Ĩ6¨Î _‰ô áE7FO"¾¹L±„æ4 CáØÔ1Ä^Î=×\A_P†O¹+OºMg c>‚cêd]{cÌ¢UÂZX'c:c¦Q4äo1à;”KSâ9Øý§si2¥·VÔ[”ìµWE9” ø(ŸÞ7¸ßÃ~¦{ú;Q
+kÔ-*]ܤKJÁƒEŠåé‹JFíêšæ¨*oŽV”Ä{ÒËžœÒžTcA|ñv§ð©å5Åú#yýbJ«{é-w‡ÛàÉñ1÷ò‘Ë“‚W¿ò Ì­i†Ø›a¯ማu?qÿè‡ y\•<—óíj#ÍW†¸bnQ‰¢õ©ÔDh}j7-¹¡¬\ÆB@ë3ÉL1¢Óß!Ìo«`®{:÷’2œû)m»ÖDåRÐdq<FfØ¥¸Üf ís…ô%¦dK’ɼ=´#sãô{Ê
+ÇTÛ§À1U—Rh©¢LµÔÁ¤øLOpypÖhr—8‰¤õ&H2ƒ@eX#œÞv,X&¼ò É2¡ló*ÎÌ^ùGd+'h
+U†®©!MSu• N‚åRRªŸU±zÆf3fcl»°MÑM)®’´j /k…à4 Î(
+NÆVÜ÷⊠L»CÚv©$,½OH~úú§î¿r­Êð„_kbã¾é:Äxs+H6Ù÷-,]ZJT[UzÑo!=¸ãùaÎu¡§MÌf:„™7ˆ™×¿Ë› 3EúZZlVB¦
+,+šÖ¨ôÒÉÞ”„eUêð$/ R$"±šŠü‚!V3D9”^pO¹—Ró_yý°EþÕÈ÷ãÜo˜`ûû7êç:oTF&í#©2;Ï‹}J@™úMu¥ºIå3TøãÕÅq†çŸ™ÙÝ»Ãwö_˜˜GBLÜÆÅÆÅ „˜â`ƒâBI0¸RÉŠ¡ÒR (5C£B"^% )R -0A… ‚"IhܤX€ªò¹€ïÜoöî X•Ú“?ÿ·s»³ÿ|óýqõcúZ¸n¹¡ÜГ¡‘¡B–ÿMw§Ë-—‚~—ž>àÒ³.å¹\D¦Ø,öˆ£È³”X§‡Síˆédw U9™c´­»šIGû’½–ó Ûaé¦ §é|]„>ÌÕÁW y¼›țζE–°#?ø ?ÀÙijß^).ÆÎ¥CÅIž{ZhöÔp8ù%S æ¼d67ùçQèqˆŽK#i{†<£Ö-¥Öøòxã¶J?ºâË¿«æÑØ&Q@iGùµ¶ã¼†š×‡½W>íYéA:L{mÚì§umý!ÄŸÏ¥¬¼†<^GSÓhz/š.iUw²s{ä.öÙmÛ—ÛÖ½ƒ 4öÎHoì™A‘Æ ôÆêåüìú@F¹›V•‘`ù uªFå×çóüG®w»*ö0Šaðh´0x$x¤0ˆ’˜žõóš1SC¸jè ÚäòÍÁ;°43ÑhÑãT:àáɾ}z«H83Ÿ¢HXöê=õÚ¶yËkæ×û=¾øcË…ŸO]X3¯îìïâ§âÿúS þýº‰UµgŸ\¾·¥äÊ؉#Ÿš4lÐüã§þáñ” ÍÍòÎýÊ3Yº¢F!¬ š_ÐØSìÿ½:v1xNB1C)5Í/ˆ]µ&Ä®Ê؉wE‚÷íè5b¾W^V“N©Ïߢu!Z-hJ˜œ@N€¬Æ`†Ë„½ÈÇë#åi>ÊY¾yfdÏÏ¢(
+ó½VÇdøTËÑ+öXA6ƒ@TŸÞèøJ·Ó€¿]Ó@þoOûåÕŽßÌ‹´0÷ÜÁ=ÇÅ[í?:ynÔ<úüøaL÷ójÂìògðÞ4e²ñ<‡
+°i‹Mk%)¦¿¯¸_–úŸñõ‹'ü#ü|¹ßTá2‡²µÎ>çCG®vö dìë¸^þ¾Ó­j³»Çõ
+H?w„+¸TcÑTë'ÖRKTeó>ž‹eœD%ŸÇ¸¨Òã5Ïf´–meŸ¢bSzúצÐ(VJ»–²’…Fú}þdùa& 1?
+)<–¶-BŒšÊS¤f©Õß”zÓ3B)ÛçÇQ,(z
+ô”T„,ˆöÏnµ¤¯•çQQ´$çfqqk±©ë¦ª'
+|I ΄÷çâÔaðÃ&›Î®Ð(vrªöF;»€¤¢qd¤Œ^„ãhi|%jQ·¢”wúç­×Pˆšå »'d)Òy[»ƒýê}öóú¹åuàW+Ÿ’¶ î EÚ¶‹ïÜò)ßf…q·#l±˜;aŽ2¥vIJ,ß±Á’°º íJ"éjÉP–MÛhŸ¶¥Í}A6›m4§(V4§øhÑœ’£ÅÎ"|©8”¼|Œ`Ñ$ì#¶ùf#”KÓò$þ÷§Ïãƒh}†ßŽ?Igãëãã[ùgüZ¼݈ o¿ÁC±k^ü  ²Æxg¨‹åÓ(ŸŠ°§De´Œ.“øHR¥¤q²Ar[Ó2½UïÓÿÖ²@Ñ^uL]RâQI[ä^Ù&Å\A•‚ƉK‚³.Ym–(³ª,>ÞÚgqK+½DZa)! ºt‘Ä€Û)–$YõŠ˜*W‹Ô!%]¥Cõ‡8ëËÉq©>ŒÌ]”[}å¤ÉöÁ“P¨ÇcªÄä4ÑL’t_hDÁÊ@/“h K(Äsâiô<M§7cwùL1)¶’Ïiß{Ýã¥5ý]¯yM…®Ñ|Šž«¹Þ×q¹ü›i‘ª œêøO9w9ñl\sÒŠ+½˜x˜¸&»ÑIW¢Q
+›+ŸÔA(Þ žEO<[|ÈKk±Ó 2ü9x$)çÂ9Ñ’R2ÙŽzEêø—±‘2/Ö“ù‰,DÊ;óI"ç%%cå߯ON|‹ºÛf”í~íp»±ûWÜY{{çݯcœp¯Ñ4%žÒâ£ëÆnï\ÔãÞ÷ñ¡L ñMI,aÛŬ֪d'T˱n±
+ºÃjy[ÔŠ÷XŠ²ŒE­<Öl¬\ÊZpÿT«’zÂþ8|ˆÓ€›À
+` îŸn`æHAÜaCT jµ²,ËeMÖ.UãY“<¬Åu®‡°&¾…5‰ û­Çw=Œ5©¾Àjøpv |z¿½Ž5¼Ê21ßvk ËуXĪaý­Ÿ±«€ÕaÍÆg³øôœd¬ÆÊ£Z¹›±ªñü·p]9ße5â;ð«óÅû×Û:ª­`â»> ß0ÌýMæ± Ï/eÄö,~Û.²GÔ%6RîgrˇM𘉓¬šî̺ °n³æÔšŒÿƧÿ
+øhü»ð©Hãë;ÎÛé[WÀ¯PÉ>óÙ*Ø·6à´Œ±FØmÖHøÒÄžóÐ
+=äÑÇ@µñ[¿Í"ðs¢ú [èíå¤ðÜÓÖkÞ»·a|Æf©-ËcÄyìáyöŠÚÅ*t|ç±ñØÿ2£=3:ª…xþŸÞ<-àkÖ¶‚U@ó—ŒFS\~ì,ªw€3ì~ íF Œ& ¼ùÌþ‚w³÷ŒÅ~…뙼\5³
+³n¬åª¹×<çTc}˜ÛìAÊ@wkø–Ž/`#æÝÞû[ŸÚ§Œ.0gxȾÜÞþ
+,˜{ä.ÄܧÌoôj4c´iôa´ý3ë!Ì ¿=Ý\öôÕÔ3çY3ðð^Ïj€Zà-¬i£‰£Y/sm]§lu"~=í{{EW½5›èj»<kbSÍõb”yýý›ò©«5qmtªö#>Á?xnƒ~¯Ã¶Â¶ÀÞ‚½ŠµÌ·SÏMÖDðb¹ÀÄ£É &’Ö»‡ïYWÞ»ºÚ26 óûj—š®ÖäÛ]쌿<#ïx±¿}Šu÷ô14©“ÿÚœeòFjÿSÚèÔH„Ô>øögøøž¹âÅq´«Œµ£Ãi‡ÒîÞ†MñÛƒ@Kò·ÀJ`fòpÚQxÚ·16˜ý‡ôr®ª8ãøwïyÝ›‡‰„‡<¥ÒÒaÆÑ,šJ±i©ô
+b‚ˆC “‚0U^:>:L¡LZ*5-*ÈcŒ¯LØA
+åÙea ÒZ¦¤†¢¥¥PBÈéïÛsN¼¹$\fzfþóßݳ»ßî·û=VÎï¢üwä<º8¨÷Âøÿÿœ÷ˆÎÚ/ðÏz`¥ÖêéWG¾ÜN6¤ëÖ³V}ê½4çFŸÈ‡ÛMøIµ«oRÖ»ÞûT>AŠÜ¾”ñßîÍRÄXÓæ•'í ¬¡DæZ=9óc²DÛœÁf¼Ø—ôߢ}œ«dšé·¹=éooÛ®ó÷š³Í—ZêåΫŒ©ÆÖ˜Ëm¤¼É?î¾$üÉø‡ž~³‘AemÃ]%}uŸÆ>пîO×iM”¼Ä@)J¨ÝÎ÷TÓg±®8ô/íp¨#—vË{H†1Ç(Wý.0cK‘ÑS¨?e££zïz›9‡„:šoîfµ÷GiHô¡|‚±·Â}Mÿñ eRd캇L³·3—þ[\ìÔ}@ÛWI™SI°÷Ã"] üOZ¬3qÿÑÃ^Ïÿ¿11NíÓÞ$•ÞxöRK¹UúºoÁZ.“R—=Û‹Ññ»&å©lÚSzgM|]%…ñMŒi•Þ`c“yº•adŽ Ö«)Þ“&®ü:èGÛLðË ,#¸©ßK½Ž2w:v$©[aÿÛA4.æÒþx(˜?ö9er~‹<ª}åhÛÃÜ?Sh=öµñ
+g’"®N àŒ”û#Ø3™X·ŸVäÝ$)ƒ/¸Ïˆ(ðÉ)…Uå7ç 'F“TòNxíÜ'¯$¡²‘›R ³!—C?ãypwq)ìÁ? /Ärק¢<ÀúH¦¢óÁé{É܃®O‘ØÂú4ƾº÷D°Æ.ä8_E¿Òš.—ûZí ¢ÿ‹ìþ¸Wsê$ væORý÷–c§€{r¦#g%ö;”ö·dž·‘öp¼™s"þa;ùêXÆ".(ÆH½ù³ÁXËKYSÊžÎzt\yÝt™m/¢®sM4ÿkøŒÑ²"æê=m_ª}íEíÄŒ¶Ò
+žGŸê·È¡ê¤Ô³®<ë$ãÕ‡4 '!o[_úUÃK6w‘øÒžÔ±”gá[te
+÷.ì|å)à1)³pF°×7¨?-e^9üDÊÝå'ØÿôÊú]ÊWšËtÇØÍRü׊x˜t—Kdã(®gãÌœ!ʲqGNÑ Ûú&œÆ~€]«$ÇÔ¼¹”µ=·€›À˜$ïï¿ØÀÉ31f
+û\
+£2üç°nú%g°ó©ŠY¼vyUìÖÿ9sÌ<Ê»ás!šâKe°7ˆq %-`—'5
+îâT/GÖèžÁï¬1ö:@ò™÷ g¯l³OÊ]Üó:ò•z÷¾X™{­¬`Þ·QÙ+!> «à“SÉuL\ Ý#ŒEGà cˆ‘oŦÐþ¦m21ÄÂ
+¡óãCýMb>_ã±®ÝXUû«´Ë|ä·ÿ0øï—jìɉƔìð×kÜÉÿ5{´ÿ‘‰m³ýo°—¿±;6<¬Çõì£û œÜ’öžÑ÷ÃÛòþ5ä‘«Ì›÷Ë=ÈÁƒ{YÙŘ…QÞŽYiÞhÝú ÿ-üË a½„ú)ê‡:ÞŠ]ú¿Ì~×¾Ö÷PßgòÆîý€ë ðwf´ßpl?þ´O$SÛìÆXop#xŸìbجÐX?é7[ýæä6dç’Ã>ê7kvšýfE\
+¬3Lyg ›Þ oP?AÜÕ7h­ÍÀtâß›A,Sný4ˆ…æßõAlkÀ¿AŒ»€M¶m¥¼>DýiñÄvrÍ‹Í´U›øÉ9ZÈ(|mM~•Íûµ‹²»ÔðòŒq5Îÿ¬²¾9â‰ÇGÐÃjYdþáñÓùŠkÉ(§*
+æý6å!á*éÓWg–¾o§?| 't”JL7ÿ+Íÿ7ýb··?~ÞecyðÁˆ­±>vnì^oU¬\#{PžÅ¶Þ â^ÛFð ¶·Âz‰užòßËôÍ"çßqž•¬×sþ‚õ{yYÒ> _èN RÜsj~r[/÷ÄŸ—ºÜõÒ{­4äJ¹‡ô
+èçKCò1iH[¼sܹ0nÄ×ùÂgˆüyfÞÖ³Ö‡3c·Azy…ÔÏ~9Šg™1ßü]³Æ›Yc?Úèßë2æbÌ$ÆLÈ–³:ö±þüL™a ò¦KòÓâÎò£y³åÃQ^Ê[Ž|ªmàEÕö h
+åL¡þ9fÝyÓõ¶öæ¢Fa©|s£{C†»z_¤ÌtöåùÚLš³?‘H´¡Ü“Q¹hngÎ ×ÑØyO¨ÓÜK®/˜…}êž{–"}Ûs¯{yíãƈ½¼³{ݹ‡9þøàÕHNg¿…4Ó&[Y¥榯¾;îÒbu…$Ð1,Óɯ¹kmMT†ô¢Ÿøe·Þ*ú›}µÑãîïbۃȒÈYP"RPjíC‰}ƒeºìÛ<+$Úßý½ˆýJeWÞ¥‘hïâ‹öJíɘg/GO“7sì¯M£CN
+<îîý6ž§‡°/b’}?E}H{_â{­Œ¿ßÏi÷§U
+¢¥¬v}ZEh÷A¸çÍÝKµ1rŽÜ\˜rÁlê©£í:'Ñ\Öse(G›(×éã"ES¼—"¬/Èõ%9˜ BîW¦(×_0×Ú-kÐBöãû0‘~»F—Ù
+eký•WÞ”/0ï\¥Ê×ôi±ÏËÖXCöÛ`m™Ê1>’3¨#Œ±É2F_19¾XˆÙBh…õD²>k…Ñ’ís«H’ÕsÈq²0R=ö0¬5ˆ¶Xl÷@€sÙeJû¢®`ÿTñ>sØsø)¿·á<ÂÒ¼+6£HBØ>ŽRŽ*
+è›èÝ\K®=]kCprƒiÈ =˳~É´i2힬–s]!l’Ä&±S$vb¶ì¯kä
+ÈçÙ¾‘Ü`ß ¤-òD¶>Ër„³‹ö/Æ+â2×BèJÍš=q¼%ôA…¼ÛsL)Ò9×YH°+‘GúÛ?Dy -í,Êqnâ-ŽitlEÏfœ A>†8{6ŠìiŒÁy,Dà\3E&Š'Qd ҵϲ½Û¯“4EŒ}·°ÀóÛa—xêœÏ3g÷¤/3l ž¿¸e‘©ÛÝÑÇ`A/ƒ½™wøm.ŠdËUd!¿_`¿1”µ2ÁoóÉö[l÷áýhLYÄñapœ-ÔŸ¡¯}ÖBW×|Ó¯¾O!fFû8ë±’l SÉ2ÇB ÷û™áCSç÷¿Åô©³ Œ•œL|ÇÄ'ç&¾ëäðÌç ëqV.2DÖ
+¼a­B/‡ù¡EFÝ%¤y‰t%3È(²Ûî…\«•®#wÉ'V+8V>R,©OÚ3õ)R)ª@)óß~È~‘Êý4ì§ Öb kà xÒ>¯«˜Wg²­Œä7È2VŒ¢O[ˆ¥¦´F‘þ®½)OŽ’IdÉúoåÕŽˆ_³^CÁÌA”ñLÎÆeÚâmw+°ÅäåÎVd›1ǹÍ|ã|/<€‘¡ú;í_aˆdòug<š3Þ™x}‹Ì¾ÏÝëôE_²®xÿÞîœP{¼©»2ü^w€mwY.#×½¾Ç¨ãŒ÷ÝÜëò’¡ö2õ&Þ|^öô5õô<i«ýÄkŸF~L]YŽ$ ‰É&Dêµcy”p®u#L»›ÃnÆÕÍMìäD*bûòÍzaï9óÂÿ~€•ÇÛÄ—¦ý½Ü(rˆžl'³ æRÌ]œY¤Ò.¡Ýb¦Eˆý_iÿ
+ÏR\O’©¸““v*ž„š¤·£2y¬âT%‡—¢&yŸâɨIIQ<“SêOEuÊfÅÓ0/å-ÅÓ1¯×hÅ3„ƒŠg
+wk–¡6Šg¡"µVq9–¦nS\ºÔÏF]ÚtÅ•ÂÝë9Miï+®BSïî¹ÏÞ¥xšÒ“ÏÇŠôfÅÕÂÏ(^€¦>#×oP¼Pø]ŵhÊèÎaVdlW\'ü™âz4ev×®Aø€âF4e U¼Xx­â%X‘Õ]‹¥jÇt}þÙ·D1‘•=AqR²g(NƨìrÅ)ÈÉö+î…´ìÕŠSáÎ^¯8 ¥Ù[§cdöÅ–ý¡âL vQqŠ]C÷ÅW­b²\»‘⺾‹™·k«âdáÝŠSÐÛµWq/¤»*NE†ë°â4¸\o(îL×{ŠÓÑ×õ±â>Èq}ª8éî,ÅYp¹*î ·{Œâl t+v /PìÆ`·OqŽÄ®SÜýÝ›÷ŸŠ Ó½_ñ Ñ?¤x0z»ßT<DòyGñ0Ñü@ñpä¸?Q<CrÒP† ¢Ða"¯C*–q,á0Zä»Z~;¯\øÄÏrü:#C¬>´‹‹héX#ÖVñcUŒêUèDDbtñŠ-b¹ãŽÑ{¡ü«Çè[/3‰8^–ÌÊ”ÙEÄ7>Y«Xsky›ò_¬Ú¶¥]l±zXÇÜ ©^¬¾º£kÅf‹v@æÑ¿Füc_ñ:U5]Í"s^ubH„¥|uøª˜ã—Q›üš1ïäJ'Âμ+{{ߘ¢c8QÁU8'Ì>SÁSÒ)¯°Œí¨€Œ+œ½ÓìœR ‹„ÃNìÉ©Õ+:9µ2©WDÅÚÝ%ó‰it"ÿ»U8WªúÍ=¥¾V¡Fv‡‰uõœUõI(õœSâ”6»¹Îš{Åu´ìÝ‘½ÄädŸû¬tÊØ|jºå¢cß ‚Ιô‰e™ü&V±1æ>ÓseOE±ç˾ôïu,±Ÿ÷ž3Nœ~¼ùœû½1ÞüãÕ7ÿøž°âÍ>>õžsŸýÞ;‰Ó7ÿÄìÄéÇ›"öN¢Ô{ÎýËß,³w§oþ‰Ù;‰Ó7UÝòp¤Ó4­QmVPZRªÍ2z0ܡѪÂÁ6Ý¯Í ù<eÁ æ¸Yš©[º¹Z÷{´êvŸá÷j†¥yµ¨éõëm^s•n9NÑÀ€aEuS÷kFH‹¶êZ£§Þ£Õz£z(ªyC~­á¨È–ç;Æ°xšÚÊvÓ°ü†/j„CÖÑKG;#z‹WýºeB–æ3u‘ókÍÚ| è¾¾­SÞê5ƒÂ*9’˜cÊŸú¬pÐÿíȓï/Ñ ¦hòó‹r r'ÁA³C”O×]öÜc¤œ•9fÚ'^¤$ÝF&çåuttx¼o[8¶—Àã ·Ù¶æ./ lu$l{EX'Í'uöhå^©cc}™VRXœ[\P’›_˜Ÿ¯5Î×
+ Çæ+(žX¬O**-*õü+åµFÛ‚yö(/(E
+YºÇ¶·ž–®Õ9[«Ì'nQk±nF Ÿ7¨Õ†-Ã.äBÓo„¼A«¾=¢›FØ´ªõ¨¬”¤iésCâ"¾åÞˆ{ËÕáåÙÐ Lo¤Õði³×È[ÕFÀm—Zn“̵£ã*½ÙÔ;´
+Ãë3EÞgÕ·JŽ³VRßã¬]³8š¼¶Øk^™–=Ïj½åè4íq¹€Sre³3>6ÏJ½+!ÛÞÍ'^že†ßoX­’“£c?óņ y¥ôºVnoêÝ Îçóyžîá“ÑG¾(^ÉHA/¤" ½‘Ž>È@&²ÐÙpÁôC À@ Â` ÁP Ãpœ†)ÝfFãtœ31ga,ÆálœƒñÒm<ÈC>
+0Qˆ"LB1JPŠÉ˜‚©˜†é˜™Òéfɳo…Ü“*1Uò0ó¥s.@t²Zy7­“·Õéu‹±KåÉ}9ÎÅy8+pš¤ë5KŸóK‡k‘þÙ*}o¥Ü¿‚Ò÷ìwš.”>hIm—¾Ú!]°k±a=6àb\‚K±—ár\+q®Æ5¸›°×a “pnÄMØŠm¸·0™)ì…[™Ê4܆íظ»pv³7îd:îÂÜ{p/îÃýx
+¡0þ@E1ü‰â(’(…Ò(ƒ²(‡ò¨€Š¨„ʨ‚ª¨†ê¨š¨…Ú¨ƒº¨‡úh€†h„Æh‚¦h†æh–h…Öhƒ¶h‡ö耎è„Îè‚®è†îèžè…Þ胾è‡þ€„Á‚¡ø Ã0#0£0c ÆbÆc&b&c
+¦b¦cfbfcæbæcbc –b–cVbVc ÖbÖc6b6c ¶b¶cvbvcöböã
+£2£3c2c3ã2ã323 “2“3S2S3 Ó2Ó33233 ³2³3s2s3ó2ó³
+¬ÈJ¬Ì*¬Êj¬Î¬ÉZ¬Í:¬Ëz¬ÏlÈFlÌ&lÊflÎlÉVlÍ6lËvlÏìÈNìÌ.ìÊnìÎìÉ^ìÍ>ìË~ìÏÈAÌ!Ê¿8ŒÃ9‚#9Š£ù7Çp,Çq<'p"'q2§p*§q:gp&gq6çp.çq>p!q1—p)—q9Wp%Wq5×p-×q=7p#7q3·p+·q;wp'wq7÷p/÷q?ð ñ0ð(ñ8Oð$Oñ4Ïðþ˳<Çó¼À‹¼Ä˼«¼Æë¼Á›¼Åۼû¼Çû|À‡|ÄÇ|§|Æç|Á—|Å×|÷|Ç÷üÀüÄÏü¯üÆïüÁŸüÅßüO!¢$+¤B)´Â(¬Â)¼"(¢")²¢(ª¢)ºb(¦b)¶â(®â)¾(¡)±’(©’)¹R(¥R)µÒ(­Ò)½2(£2)³²(«²)»r(§r)·ò(¯ò)¿
+¨ 
+©°þPU1ý©â*¡’*¥Ò*£²*§òª Šª¤Êª¢ªª¦êª¡šª¥Úª£ºª§új †j¤Æj¢¦j¦æj¡–j¥Öj£¶j§öê Žê¤Îꢮê¦îê¡žê¥Þꣾê§þ ¤Á¢¡úKÃ4\#4R£4ZkŒÆjœÆk‚&j’&kŠ¦jš¦k†fj–fkŽæjžækj‘k‰–j™–k…Vj•VkÖjÖkƒ6j“6k‹¶j›¶k‡vj—vköjŸöë€ê눎꘎ë„Nê”NëŒþÑ¿:«s:¯ º¨Kº¬+ºªkº®º©[º­;º«{º¯z¨Gz¬'zªgz®z©Wz­7z«wz¯ú¨Oú¬/úªoú®ú©_ú­ÿÂaZ¶C:”C‡Øâ€Ã8¬Ã9¼#8¢#9²£8ª£9ºc8¦c9¶ã8®ã9¾8¡9±“8©“9¹S8¥S9µÓ8­Ó9½38£39³³8«³9»s8§s9·ó8¯ó9¿ ¸  ¹°ÿpu1ÿéâ.á’.åÒ.ã².çò®àŠ®äʮ⪮æê®áš®åڮ㺮çúnà†näÆnâ¦nææná–nåÖnã¶nçöîàŽîäÎîâ®îæîîážîåÞîã¾îçþàäÁâ¡þËÃ<Ü#<Ò£<Ú{ŒÇzœÇ{‚'z’'{Š§zš§{†gz–g{Žçzžç{z‘{‰—z™—{…Wz•W{×z×{ƒ7z“7{‹·z›·{‡wz—w{÷zŸ÷û€úûˆú˜û„Oú”OûÌÿÁ P
+¤D*¤F¤E:¤GdD&dFdE6dGäD.äFäE>äGD!FE1G ”D)”F”E9”GTD%TFTE5TG ÔD-ÔFÔE=ÔG4D#4F4E34G ´D+´F´E;´GtD'tFtE7tGôD/ôFôE?ôÇ
+¬Ä*¬Æ¬Å:¬ÇlÄ&lÆlÅ6lÇìÄ.ìÆìÅ>ìÇÄ!ÆÅ1Ç œÄoøàOü…¿ñþÅ)œÆœÅ9œÇ\Ä%\Æ\Å5\Ç ÜÄ-ÜÆÜÅ=ÜÇ<Ä#<Æ<Å3<Ç ¼Ä+¼Æ¼Å;¼Ç|Ä'|Æ|Å7|ÇøŸøņbh†aXIÑ ÇðŒÀˆŒÄȌ¨ŒÆèŒÁ˜ŒÅ،øŒÇøLÀ„LÄÄL¤LÆäLÁ”LÅÔLôLÇôÌÀŒÌÄÌ̬ÌÆìÌÁœÌÅÜÌüÌÇü,À‚,ÄÂ,¢,Æâ,Á’,ÅÒ,ò,Çò¬ÀŠ¬Äʬª¬Æê¬Áš¬Åڬú¬ÇúlÀ†lÄÆl¦lÆælÁ–lÅÖlölÇöìÀŽìÄÎì®ìÆîìÁžìÅÞìþìÇþÀÄÁ¡ÆáÁ‘ÅÑñÇñœÀ‰œÄɜ©œÆéœÁ™œÅٜùœÇù\À…\ÄÅ\Â¥\Æå\Á•\ÅÕ\õ\ÇõÜÀÜÄÍÜ­ÜÆíÜÁÜÅÝÜýÜÇý<Àƒ<ÄÃ<£<Æã<Á“ü¿óþÉ¿ø7ÿá¿<ÅÓ<ó<Çó¼À‹¼Ä˼«¼Æë¼Á›¼Åۼû¼Çû|À‡|ÄÇ|§|Æç|Á—|Å×|÷|Ç÷üÀüÄÏü¯üÆïü?ø“¿¢P
+­0
+«@%Yá^Q‘YQUÑ]1S±[qWñ_ ”P‰”XI”TÉ”\)”R©”ZiB&+mÈ¥ ™«ô! ”!d¥2†¬V&eVeU6eWåT.åVžoÊ«|ʯ*¨B*¬"*ªb*®*©R*­2*«r*¯
+ª¨Jª¬*ªªjª®ª©Zª­:ª«zª¯j¨Fj¬&jªfj®j©Vj­6j«vj¯ê¨Nê¬.êªnê®ê©^ê­>ê«~ꯨA¬!ªa®©Q­1«q¯ š¨Iš¬)šªiš®š©Yš­9š«yš¯Z¨EZ¬%ZªeZ®Z©UZ­5Z«uZ¯ Ú¨MÚ¬-ÚªmÚ®Ú©]Ú­=Ú«}Ú¯:¨C:¬#:ªc:®:©ßô»þПúKëý«S:­3:«s:¯ º¨Kº¬+ºªkº®º©[º­;º«{º¯z¨Gz¬'zªgz®z©Wz­7z«wz¯ú¨Oú¬/úªoú®ÿôC?õË!åÐã° Ó²ÎáÁÉ‘ÅQÍÑÃ1˱ÇqÏñÀ ȉÄIÌÉÂ)Ê©ÆiÎéÁÉ™ÅYÍÙÃ9˹ÇyÏù]À]È…]ÄE]ÌÅ]Â%]Ê¥]Æe]Îå]Á]É•]ÅU]ÍÕ]Ã5]˵]Çu]ÏõÝÀ ÝÈÝÄMÝÌÍÝÂ-ÝÊ­ÝÆmÝÎíÝÁÝÉÝÅ]ÝÍÝÝÃ=Ý˽ÝÇ}ÝÏý=À=ȃ=ÄC=ÌÃ=Â#=Ê£=Æc=Îã=Á=É“=ÅS=ÍÓ=Ã3=˳=Çs=Ïó½À ½È‹½ÄK½Ì˽Â+½Ê«½Æk½Îë½Á½É›½Å[½ÍÛ½Ã;½Ë»½Ç{½Ïû}À}ȇ}ÄG}ÌÇ}Â'ý?ÙUØHn…ïâØŽ¾q²Ù½^™™Û”¹ÍmܬwsÉv“ÜÝná*{ä­ìì:…+33333Ã13333T3ÒŒ”ô‡GŸžžÞ{ß'i¬9œAŽ$G‘£É1äXr9žœ@N$'‘“É)äTr9œAÎ$g‘³É9ä\r9Ÿ\@.$‘‹É%äRr¹œ\A®$W‘«É5äZr¹žÜ@n$7‘›É-äVr¹ÜAî$w‘»É=ä^r¹Ÿ<€}°/FPÁ(ª¨¡Ž1
+ÆcðX<ÇðD< OÆSðT< OÇ3ðL< ÏÆsð\Láyx>^€âEx1^‚—âex9^WâUx5^ƒi€Í˜A¯Å,¶ …­Ø†9ˆy,`;^‡XÄ–qÆ!؉]x=Þ€7âM8oEØ`è ŽÝèÁƒ
+ŸÆgðY|ŸÇðE| _ÆWðU| _Ç7ðM| ßÆwð]|ßÇðCü?ÆOðSü ?Ç/ðKü
+¿Æoð[ü¿ÇðGü Æ_ðWü Ç?ðOü ÿÆð_Ž#p$ŽÂÑ8Çâ8p"NÂÉ8§â4œŽ3p&ÎÂÙ8çâ<œ p!.Ÿ—â2\Ž+p%®ÂÕ¸×â:\p#nÂ͸·â6ÜŽ;p'îÂݸ÷â>ܬ}¬}­«bZU«fÕ­1‹X°¬F&lRïÐг+~:µ¡O£„SÏæÝnfSߧ ƒÜ£Ï§<H˜É)VDmÞ‘xí÷£po®áÓN´ÓaABZv˜Ðv¸ÂHÜMÜaßeAÕçÂøܶ=&†ëÓrFʶ6ÝŽØ
+«Ñ¼©O‡N°^ÊÖÚÜáQ'õ»Ûku4&›ËLÕf:T´lÚã$
+EöúŒJÖT-SI›2)ËÒ,ƒ±Õ›ª&ÛZ3^c2ǬQ–c”5[Fr
+$|C¡n›yážNŽ“4
+˜-G·‘\G·´i4êŠG­•pÏf5ž7õ–b“–dÂ¥|-U5—íHkëßmm5rì6ð6]™ÕÓ¸Ò‹hmŽvÒ„Õ¼¼±æ OÏÀsZkËÓ˜Ì öwÜ„xªÍˈŒ8oD 4®ÍK©s cVšSaS_P
+„J©@(×rÁnËšIX ÚÙD2ó#sddÞ!3G²Y”sb9gÑP-Öxl©Ãlîyt,Q ¶$ Otá£KÙR&ÙR.Ë¥LåR.+"©"²,‰¤yS]ŽxàTÓìI–KRiêËj©SuR6JÜcàj\Û% ®Ê¸»Ê»Z ªN\]pÃ(¨†ùs9¦Ù³æÊh5c/8ÑxL¼TVXÇoåÓ×5eæ\ˆ%3åzÐÈ*Ë_4¢cÙœE,æ±Àu7 {Y›‰%Ú n84J}¦™ïX¾&ù¤0ÉËÈó–E‰Ôúe™¥ Yœ8ô¸Æ± œÅ™PýL˜,šš3%p5 ƒ0ž°9•Uå]L{}—æÐì%ì1NÍ~ÌE„Ü<ÖLÔx+T¨±¿1åxcÙpÆ‚Ïé´‰ ÷µÉªy²ÑXB«³TP««D£»„©"U—\F³LÕmâ=žr¿mÓ‘Ó‘ùtä^W©G¶óÊ7¬.rǧ•%šÖU•í.¯l¿í1o™%L*Ò
+M=‘Ôc‰´L®ÒëhBÓàP»˜ÓHxŸ¹*˜%”ä^¥Ñ HÒ§q™Ñ²„¯(œˆ ‘ÄãNöÖt§do“ìùéÀ¦¾Ê-L¢À!÷•Ï䀊[òP|ª¨Ðû‰ÿO±©#.é^‘®OW™Jaù© .ñ„O]Ú§=Õmø,êc›²ŽK{¬›–al9PÛ_—r þaeÛ$öuÛ,†~(M$ô˜Dà^:TÐf./ÔâAÙiŠ›¨¸ô“Äfg+*ºØÞ íË& }ñPåoÌ ñù”í |¾Ë#>(4…´E\Æ2"4ÖNbúÅ@ÚN‹†-”ŽÝB[&¾ L´«HF´ËÊ 1SUˆ#Ëq×*v¡Ñ™Hû}•]qM`*iÓr¸Ï¼riÊ ;æª\d…{lUlµ}‰[8’¤DÃÐV‰{´›ÿ÷«^ÁA|¸ø¬pÊ=|¦!%šÒÀœ-º91»ØgÐÅŒ÷™á?ÖW•O&±¸S#VnPQa©k)=2- Z토%¾TGž&/¿ÖŠCÓ/ü˜‰;J(î%²?1HÙÿ讲Þ6r$ (ŽìÖ-ß¿"X){>Xy0˜`¼˜]?äiA©)5ÓÛdÓŠüë·X<»Õ ÄQÕÇbÝM²dÃ;ÎHùaEouJ’¦ßþh„xs$Ç.ñ=mO4ë
+b¹ åÎ µhhV­gòÙ¥”Ñ‘?(-í:IÌ=™@£øMŽL„ßþA˜{$)u)€fwt…ðªÕxõžòŠÆ‡ÌÓSxÌ)uv÷Înߊ¥rOAÇYWJÔ¨â®0‰:dji
+”K%\–NNj¶'>\™Sý\lN¤ì…-’–F1COàÌÓ­`s¤råR§-·æaHrI:ZlÜd‚ºRï9¸jIæ ‘ì§S ³ó‰²\!6Ì)^ÂI¡Ç=}¡äž>¬„kŒê{¡áÖÀc“q%I•Êζ\sV54•Ú€ù.205ÿ¨ñ —ÔŽ‚©5ô^ê›ë(=EG ~¾?F·ZòÃw6HTÊÐ ïž:¸nXèÂ`d­¬;S°{àÎàÊüôH0iÝdK„Ïô±Ä<TN\¨¼/‘a}]DÅB`*fy_6³ß×Îì
+h€¨ŠFA(%òIÎs û¼9|ºHOV¤ Ì1–‚©
+è.tR4;ª4…R»ŒÃ¥&H3#3± È\I.y絜øÉ
+S-›¤®¯ËÜЙކڗ@W ¯Ô.¡²üÖ™vÙ¤‚Œ£h’ò”¢‚¤1W'¸I{S£Áù·®‚Œl²å[íŽÉHj^èÞ¤F£VxÏ÷ÆiDºDƒ&m€Â U[¯J^¢4Ü‹'`"¸Pˆ Z¼°I9™nâA‰qH¡¤ñ P£#ÉxÆjô§€„*#zÅm&£.6ZžÖ„HUq«:¥áñÆ ’›–á déÅ™Y –vlHêeUP6_p,`íØÅÞÌ(Eƒ™°S·DPW£T&·×” ó‚äØ£'[òR‡Xr¯®$a¹±™
+U _ƒk*¯ÁK¼|m{©Ú^¾/Uðòµå¥Š¹áwT;<áOòÝû|ò>êOá[ùn¾•þ\ÒJÖdG/³SÑj55¿‡/‘ä38«á,èV0¥NôI*¼ZS¼v
+ÞiÕîtµêƒ2Á/
+&ÈEÉŠâsE€¼¬©¤ ‘Ÿ죟Ž¼J*z„±ä)ä%ø§œÕÃF¼Kÿ)Èæ¥/"ãÃJ•p¾_þK7HAáI-ÞÙNOù§—¬Là‘È074—H©z„¿Bæf1åÇjlµàÌ£(4EVç&,jovÀ3¢§ÏF ³aÖƆ5ª¾Ò?Z©Ñ—™i}cÏ©zæéÈ°fõ¶ r Ùê]SÇ àg™±}sµ#µ„G¾LÙ;“0âÊöþ—éå^k‚£Zðt×ÀŒ=Uú.7*†¼n`Ã8¥PBj”B2‰óWó#$ººÈhQµFø^KZ©¹íl’-ÿ¹4•ˆ2:Á%^Ó
+–ÇÛ‚ìrù¦ˆ ã#\ÞÔÐ ƒë‰Âf1
+~à:-×oðvideš‹B:KýüÎZH½M[ˆ¤^´Œ öÖ–Éè;‰‘{x=‘ý™zŸi¼~S[Õví>ã)3îÄðƒ â ÔT1þˆŠ{;²”É9:ñqÄ£wÑÆë:ÖÀ߆H»`GÓÂDKùxгøÜÒÅ w¡m:Ñ›6„Q¶!t¯ a¬)n z÷àçZoLÜYz ŒBl/tàVç’
+MÛ‹´©`Ê Y4¬¦YÌúÎDÄ—
+¶þº„y±† I‰8Ä*à>È#þùœîa èý‰
+’miuâ%`ë"\c±e
+[¢õÌt `-c
+²J¤£®~±ÔÕoŽÈñÍ…#~wDåˆ?!,‘üÇi<õ¯
+|ª\j
+^¦É/~í7Oåžúæ©ÂS¿{ªòÔž> G,Ÿ:ÌjIΧÎÔ’1ÈÝSψ³º#}è™Íõ™Íõ™Íõ™Íu¯Íu¯Í~Ù¯Ëîl»Zv‡ÛU,c£§gÑoz£§½Ñwm®Ïl®Ïl®Ïl®{m®{möË~]v§ÙÕ²;ήb=?‹¾oæ]Ýõ ½«3›ë3›ë3›ë3›ë^›ë^›ý²_/49ÑÿíÊí— N€ÁÓj°Y ^Vã’¤)!‚lÙnf×pÙ"›Àgi¨isÔߨ4ܼÉ¥¿ñ¼~EllA¥ÜR«ì!
+«´.%o!}é¡ß¬ºæG•T'ÿNx¦’L#3¬È9ÈTêA¦ROd*Y‘©Tä™
+®u%ÈÐ…²Â}¹„m=ÏauÃ^ªyš”y'_UIž×^Á韉›rÃÃoTüÔ‡ûcÒç_iÒHŽ¨X³Ft¥¨äcpWÉ"®„`‹z4•Øö¡¸YpÄkFå”ûÝmšHÌ´;V1,’}Ã"Ù3P£ôn{U° ˆõ3cÀtqNqåþeðƒÆPQ?Æ
+cbq"
+";s\hŒ "M
+
+eqK-
+jq¯,jeqO-j"Ššü Ò+I¯þ‰ô5Ò7H/‘¾Eúé)ú ¿áK¤>ÒÒ¡æðj>ãeõš¼¡gx"Å&_
+8"û3ob{eЛèÐcšïO01±]úÌ”ºWzö3ŶßËÀí9öÝZ+Ö5ú j+îC³‰CîðCS¡™Þ½s{±Á_3H4ï\ÓS'!bë“$ ÙW?©\jå>é•[(¶óYi¦ ç* £”ÊÝÏÚSªXï³½6©´¾È—rÏ}Ñõ\êz~Qù,Uæ_µf¦5¿*ÍLkêÌ2ÙW;³ÌÞ57¢Î"¯^®yGâM¥œˆG7æ¦ÍMáÆÜÁ¹!´þ’d%kð—žÙJ³·š-ô|oÕ| µR·ÖJÖJÝêzšý¦ý—Úÿ7å¿Tþ¿‰4•Ž[Zò7qM0Æ©|¢|j¨Üƒºwš­t>w*ŸJ­ïžD¥×÷Î^ßÊô%LŠ5{÷ÆWkÞ»·ýÖö¾¹7öM­y÷^Ö¿ ãÖTný-t[kA:«™®®õ]l¤ÖF’ïr×lä×ý]õ—MÃu¿ëêÊv¾+ÏUôæo­ùòù[ùüWknÛû¡‹ÐÛi¾óCÅÚ©X?ôzí4ëöS5ÁžløI÷dËÿ;%<æºà Ŀzù©t€tˆt„ôé+¤"}ô ÒK¤o‘¾C:F:Az%©ñ}Œïc|ãûßÇø>Æó1žñ|Œçc<ã Ðÿ
+Þœ)8Sp¦àLÁ™‚3g
+ÎÔ:Ûð(\ GºAü )GÏ¥ #±p¥Ï¢eG×d¨deÏÅ"‰¸Ãb·åR³MHæÍXtú+ %WÍÔÅ%Š¹s¦gæ)‘y2 è䨳¢Ç™Ã£¦\‡™¡\ºú`R•}””C”£:› ѽ¼é]gµYí­¦¦¼¾<òE ŽI1·é¬ÕXEÂç2+·t=uµm¤&>‡3×ý:±=æÓPŒòî}„{&Ã\]ÂýK¸}y5¬±`Ë(ô`RèÕåÍ´vO“M÷€ ö¸uåøѸÑêÓ0‰PÏh÷ ^L»ò20UKýÓð‹È:ÄÛ†åe-kî¡'ã‚ÉTNPZ(§µ0íI¦®É­kó̵yæZœºo]ã[ׂÌ5™ºÃs®É]×zp­×zVKºa3úCÛVýëJÀfÓÖÕ出ú×ñÊKâ†Qº†žsdÿN¾*ªVð@-^µ|]vôœå춚ø­Ù¢9ßZ©^úº~ò5 l™0)BÓcýѨ}f"2½=Ѩ¾«í¥ÍãïaäüSï²£§§YMƒéMËÏ"¦®böU' mó+aÞŸ¾ ©ŽÎV,’‚yŽ˜Ï!ĉïk8„Ž$§›AúM;bŽ°·]°Õ*
+_ò½¶ÓI2&f±Û°=a/®vuS×=&9uÜÎbE¬£Îႈ?ž3šÅÀåB3Úëñ«? ½úŒKSk,h§y¨;ÔzóÁiæHߌóÆcÞ9ãdž]þûÌÓSø᣹
+P91å ì›ÉÉ“Ëgj_Ût‘ðh<'›¡=,š¬`žÓûH¦°]¥s[ì±#Šö9ù°ûÍùf_;{Ú—6{RçÖ\·´¢}z»ç±ØSî÷”pW13h’•“ô
+›q“î·½>“]åí77 ,J|%2S“2õ|\2kö¾vñƒG{ ‹×±1Ù?i°m]ðfzÙ2»;¢†dW]™D”´ê‚ÖmT”,{TbÛ]³ú­^bŪ¾iÑöÕ­Õã cV#ÈQA®¥fU¢Ý3Ê5ÊWS6^(ɉX†«ó\=u#¯‰€4J¾mÊÆ…çqÃpY]Â÷e¾†”»)Ÿ¿±6îá÷× ¨<¨ÁTGÔ ½©ÎPNLÙp¸'1•{=A3•ß–¼B_‰¨Ð@ô‘A ªúdÞU7¦l$õöpM°–Ž0ŒÂèä“iV£f6£¥Uã¨T6ÊûÊ“zÖÿ“úÞF cÕ¿ñʲj!Ù¤òcuöתڦªj¥b¢Žnzõ¡þ%í«5›Q”Õ~r¹ñ%[Ë‹]åBíjS(¢ãK&nJÄó;¶¯-K4+Ø×ø¾vú<aj’·;mWy—µ)zý.)×Ï?—øãîkã²QÛ%bóÁuYuWÌ mw™ -½Ì2+Î[{³&3;,Ú“ g³¥ËbÁ²ðsᔉÜq8—±XlŸD¬Xj1·¸zfûZ[¿ß±tå§ì÷å_¼8¹c;ÂrGxO³Xþí{û¯Þ4ãÜ|¶¬€dfñ¼uzÇftŒXŠí‚Yî*çO%ËtS¢YqÉ’,ÑK¾•ûZ둲ÿÖo™·^£ƒ{qŠÃáÂ=|¥öéÊëö½Êüx)<‡WD¥[
+ÎlѤÕ[3m‡jŸ7ø6àTTŸU jÿÔW8F„¦|C´"AÓɼ¹ŽcÍÔR·˜FZlvêP´x•bÉèfƤ\¤?Nµ`3jKâ»\äÉëm[g)äNBæ™9䨗KR‹NWÏ|Æ)„)‹Ùt“I­*ßêSG³uÍÚ—ÎæE~©ØÉ3üÐ95—$u³é°Ëc~ª.ÒÍ)gùoˆHŸ5Í1Á =&ÔõˆL½èVê¬þí×ûWþš«–¹Êbê¯:pÂ5Sí%ÕëÉ
+µš¾¨×‘jŒ\¥ëç9%NV4Óaô[1‰¤r'*6kiR«KµDú­$“ܦ¨®]ºß&ªÕIf*¿ ‚šv5Ù §A ËM‘õŸY-Êjë¬öš9º`´2ÝËjqV{Ik´è(Mˈ"æ"¡¬†²#©B²Ù€“éßJFšö†ü;F99Së‚6!¡Œc~óy^riŒj– ÁlÁ4¤–/œÌ k„rØ¢ÿI«Mýõ–Åv$V²…~¦ŽdÂëè¹Ñ§8ýÓ+Ž>P|ÎV¨¨Ûe›L•úö3Œ]"þ=Ÿ$÷ßÚÿïí[:zÐhiI(ß2uQ¦^|-Weß“G¢<T`ÙejûŽå?m/‹¦L^0é±7":FìR§ÿ½ëA\¦¾Ãq`OËõ£?t9òuѦL¿¢ýðK_ûÖÕGZP±x©JSVFzyŒõS¯¤ÊD?§úùI=›ŸÔ²A~©ÜéKýÂcþÌAn¯þ³õõJíèÊWÝt¥žg™È£›ES®ÔBdžnò¨ŸR=Û…{å¿úùC?_uô£¥s]®Cœ—Õ˜æâ™×$*o±M³¨f2Ÿm'‘Wד¬z:-œvt€,(¶ªS‰¹ ³©Ž>éÍR‹˜»a4=³Øt>®x¹e[)Å¡GW|½Ó‚tjÚA¡MõÞÒÝž„>_˜ jൕ+´Yæ"Ö1ø4«mï_ÊzC3|†¤þÓ XÉ'$Ç¿&³s
+xšQyVýЮ´7Åÿ°Qߨ¹W屫‹•ÚÈG#ݼ¡gÎ8}læH¯Ÿ”ûbÒÔ«üÒþçB¨qóýž’úZs6ÌS?õÐ!ò‘£8ñ¡¤AIý?甼øÀÛœ¥—
+Èöÿø®¶9Š+ÜÝ{›ûew<{³Y›( h¤Ä2R¤_¼f7`l¼Ž×2R„z¦kgÚ3Ó=îË®gArˆ)O ËÚ%oü4« µH„%!WÉà)"<å™
+,ñЯØzªUð Añ¨²kz–ÏKYèM¯ñˆ9³&Ò€ŒÉDH*œ)G§Ÿͺ³t¿‘ÀbÚŒ-Úé—Ĉ‹¼)\ÁU`/‹(YU1ÝäMn“ÖâdÖõ@wÍ¡_t9qå(÷’|ÆDžÙ …|.¢9ÅϼÛDtwûDO_»^MíZ¸Õš\„k¢¹.¶åµÐ²M÷0RJÑfFüôOÅäWDójd©=a©úŽk·~šÞÐi3äÛ3 cÚ"ÿÉñ<WXó’Èlù©\±Å:Wº8ȉò†ºkBÚ5Y æp’²Ü</©²<MÙ³®(5ºº1^Vf_PÔ‹Šº ¨—uQQ—õ²¢.«.?¶Ã¦‡'Á+Šºª¨kŠº®¨We*EÍèX"¾ˆô«£z+òv«‰MEÅS“y¾®8~Í%7—¸ôjf[‰aõtÕ—TÉ£B]µ‹›Ü¥"¯²Ú6q¥Õä¢E)kVÕ=VÂõÇ„Û‰íÝG xö1³q"¦ K¹F¥êE·b:ª)*™œ“aLî(²(¶¸ßW±ÊãÃw“L+Áä¨C™ _•EÏxR]P
+F´Ÿ§]ðq3É°Ðs‘d{(+†3dùŽÝάÇPP:¬¸m¶|»o£Ü±ÛNå…Èš¦ƒjó·+—Æ€ÙKêÚ+膪%”•Çôãêíé–é1w;k¶ZH)w»dZ<ΰy“µ‚™–i™HG3Ïî³¾k呹HºÔra/!Ø¢9àsСβŒñžÛ7–U%#¤°J€{2Ð9èØžEu/'ü ¯v·ƒAœðóÜ´‚Îm»¡'HÉO¡"ÂP,Ìe»Gf’¬U`á ‰Ð(*[“X gI0ÇA§Mý}Û²zŒgr° Ô¸$@!Åÿé2[awå±*²dW%²Vµ
+ü½GL9©-uî1O¹†¬zK'`uÓ@û]ÕD'M×Én! çèüQs¢íÛUžm' ïø³ï';;z±³#E”plGM* z¡ò%¥ÎG™˜v+Dt“®'ÉÒGù Žxɵ³±³!emg[ˆ3)êJºÈ¯™ä
+tÑbÆÞ‰ç@55‡«¦8®ššDª)†T“L¤Z<T“\¬šDª)Hܨ[!ó©Úë›^7o;8ÏmšN»ÒôÌV—´Ê­±¼|ŽÐõ“]ò4Ã^Eaø'F«V•ƒaÈ]é~Vn….¼K³'{f9
+Êíºžu–e^ ºY^8€‡#'’õwC§$¤SnÄìãˆÈ!dPàÐEcøfZ=×gá "™ìÌ
+QqlyrdP€×š=¨GÚò4T(rŒƒqr*Ъƒ\×cfòŠµ*4sW&®U>'…ð—™Ùtw(õ-òû/ûkä(pÒ8 –ÂAr
+y¤$À¯„dæ;®Û˜‘¸ H%~Œpèé¹Xû¶ZºéÙV›©É8 ÓCˆŠŒ%qr\¸W\^ ‰¹2w<ÐÄë™CÚ%Ê{©ØˆÑš@qæí¬ûn϶ðLÓcEpzpÉÇé{JβÏÌ6NAòÙÛÒœ.t44òòu„ÀîVÉÁÃiÄD¦ U…›€_’›ÔÅS¡µi±ÇT"yù‰Aæê¸}$Ü/ª *pwˆ-|éÀ•“ã8Aª…ƒ¸¢˜Â"¯€äHÇ)p–¢«CWH‚9¼@^Å‘ÔUH+¥^zY ŸHÃQäSKñÞñ]'i¿INÙô#M'Cú‘&-úuèקé4Ðo‡~·ñ›A~D/}
+™E‹eÉ7A fe¶`hÊä2~D”®we‘ñ}Ø´¦Ø#­Ý8Ò~†o«±ö´¶iÌiºöþ.¾;øÞÆ÷.¾)M3fµ
+¾“øNã{ß”1‹¾º¶ÌÛÅGô ³ùÕYŸ?þégø½þF}ùõ7?þôÎ.~ý~=¿®S_~ >âŽ3¡uõ®sÇ[
+ÂÚ±ãí›ømÛø±NmÙíÜé¼Ý™ÐX…dï±/ÙÔ¦³Î/o--úõ½çO ñÆ}ãÀØÿÍÙ•ÿ>2GºqˆæÜ‘®½ŸË­~µ?ÓX+gigµ㤱¢Mk ãýmBk<z`œU««¿3NÇ#àøè§Wïsbq)"Ê 9®æ,ææxÏÒ¨XBÏ"zhÕºþÍhº‘[;­®éúgú'š¡5ôO£öŸQûqÔ>ŒÚèñöïQû·¨ý+Zì§ÿ%jÿµÒ?M6òkYý8¯<Ðešñè¡þ‡ÑW!×ç VNEDm^‡ÙÒjù·ú›Ú
+>CoŒ&`«ïîRóìáÝ ™ì»‡ÙÜ*Úïæ ¼?Ò—Gû§VŽôS£»+hD3ù>Lû-?zôàÜÙÿÌέþâ^¦ñ«ýÉÆÝ{zC{Kk?׸»?Ѹ·o4~½fåÝwô‡_|u0q°¯7Îí/.¯žÛ‡± gôæTãÈè>œj¥›‡Xúç/j:¸åÃgž…|hëuÞŽ
+E~ ÇF'ŸŽˆù…ÕûÆ2ˆ ˆfÌê ¼£6ÊÐQÓ¿q™¿>„˜ÔŽpuïë_è:þ‹Qmqu-§ÿ^ÿŸÆÿè(Ÿß¶É0Ž¿fÍêÄI¨YÚ¨uh²®Š³µK¡ÃëK릫&”Y×CB/Câ
+:±N€nÅ×^Ðÿý{ùû¨.{5ÞÜÓ4Óg"éÑRö¢QóÁ®lìÖ{}‡â¤ï°“Ëù};ÏšŽåTÙp)ÓºwtÜŒß#ÏÑö§ÜxÂ=ƆÝcÄ·+ÛÍmùC‡´=0`fÚ³íËíöVûn;üþ*žžWUó|0gýä´?g=˜»ž/t˜î0ÛYK½?eÖmÉhÚ!㲬Ú~+}©T1/Ù5oBûðaÓ²u >†¹÷$Y
+I=L¡‡´Gƒ÷i—0ߣNÙ,TdI¥])Á¢R1}`˜ö¤^Pe\Š@aHf¾œîC—é ú9×é3úlÒ'ô)x ü†Eé*ì_ƒW`ÿ
+¼†˜«Ð/Z‡šÐË4ÍbXË4qzñã4AÇÁ¥càiðUÄÏÁ>ÎÀn§;Í@ÓÐ8T°Öyú„–šÒ´—´ä‹š:©)E-r\ Ohò¸ÆŽi£GbcGÔ¼+êH6–˪Ãz,£«j<¡Dö)ὊêQI
+““º.¿&oÉå®ÎªUNÓP4u`0ªÅE“¡þhçùå9>Â3|˜§yŠk<ÉUáa.sÆ+“$’eV^*‰ç<[“F¹%gEÑ(‹He¹ê}\ÃS!5ðgZ¡FK’ó¯/WqÒ=³¾ŽÃÏDùœýQÍ‘XIPCdÏV=Xgª"ÓhÅÙRÕ‘¨T«Õĉr¥êyÕŒ!ñfnõ¡š(zæP•ÅÉ3"-Ý®••§GÎØè‚È/¼!
+ çNuõæå'¢nÿö›w»Øº VVºz²'ëýŸyŽg$|rÑ|5]ód"%,”É‚¨§‚[t'³ðΩýÚÁoüÞÁü顳Z'âwf±T½‹PeY f1øƒ) ”lé?
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d' bytes='2989'?>
+
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
+ xmlns:iX='http://ns.adobe.com/iX/1.0/'>
+
+ <rdf:Description about=''
+ xmlns='http://ns.adobe.com/pdf/1.3/'
+ xmlns:pdf='http://ns.adobe.com/pdf/1.3/'>
+ <pdf:CreationDate>2006-04-02T13:01:22+01:00</pdf:CreationDate>
+ <pdf:ModDate>2006-04-02T13:01:23+01:00</pdf:ModDate>
+ <pdf:Creator>Adobe Illustrator 10.0.3</pdf:Creator>
+ <pdf:Producer>Adobe PDF library 5.00</pdf:Producer>
+ </rdf:Description>
+
+ <rdf:Description about=''
+ xmlns='http://ns.adobe.com/xap/1.0/'
+ xmlns:xap='http://ns.adobe.com/xap/1.0/'>
+ <xap:CreateDate>2006-04-02T13:01:22+01:00</xap:CreateDate>
+ <xap:ModifyDate>2006-04-02T13:01:23+01:00</xap:ModifyDate>
+ <xap:CreatorTool>Adobe Illustrator 10.0.3</xap:CreatorTool>
+ <xap:MetadataDate>2006-04-02T13:01:23+01:00</xap:MetadataDate>
+ </rdf:Description>
+
+</rdf:RDF>
+<?xpacket end='w'?> endstream endobj xref 0 409 0000000004 65535 f 0000000016 00000 n 0000000088 00000 n 0000000152 00000 n 0000000006 00001 f 0000000321 00000 n 0000000049 00001 f 0000001342 00000 n 0000001421 00000 n 0000001856 00000 n 0000002265 00000 n 0000003493 00000 n 0000009907 00000 n 0000022661 00000 n 0000022684 00000 n 0000039791 00000 n 0000039814 00000 n 0000060651 00000 n 0000060674 00000 n 0000080690 00000 n 0000080713 00000 n 0000089845 00000 n 0000089867 00000 n 0000093572 00000 n 0000093594 00000 n 0000115452 00000 n 0000115475 00000 n 0000137402 00000 n 0000137425 00000 n 0000152310 00000 n 0000152333 00000 n 0000160521 00000 n 0000160543 00000 n 0000160572 00000 n 0000160601 00000 n 0000161657 00000 n 0000161759 00000 n 0000161996 00000 n 0000163215 00000 n 0000163323 00000 n 0000163559 00000 n 0000163646 00000 n 0000163744 00000 n 0000163901 00000 n 0000164010 00000 n 0000164036 00000 n 0000164065 00000 n 0000164087 00000 n 0000164173 00000 n 0000000057 00001 f 0000164199 00000 n 0000164297 00000 n 0000164453 00000 n 0000164562 00000 n 0000164588 00000 n 0000164617 00000 n 0000164639 00000 n 0000000065 00001 f 0000164725 00000 n 0000164823 00000 n 0000164980 00000 n 0000165089 00000 n 0000165115 00000 n 0000165144 00000 n 0000165166 00000 n 0000000073 00001 f 0000165283 00000 n 0000165381 00000 n 0000165538 00000 n 0000165647 00000 n 0000165673 00000 n 0000165702 00000 n 0000165724 00000 n 0000000081 00001 f 0000165841 00000 n 0000165939 00000 n 0000166096 00000 n 0000166205 00000 n 0000166231 00000 n 0000166260 00000 n 0000166282 00000 n 0000000089 00001 f 0000166399 00000 n 0000166497 00000 n 0000166655 00000 n 0000166764 00000 n 0000166790 00000 n 0000166819 00000 n 0000166841 00000 n 0000000097 00001 f 0000166958 00000 n 0000167056 00000 n 0000167213 00000 n 0000167322 00000 n 0000167348 00000 n 0000167377 00000 n 0000167399 00000 n 0000000105 00001 f 0000167520 00000 n 0000167618 00000 n 0000167774 00000 n 0000167887 00000 n 0000167914 00000 n 0000167945 00000 n 0000167968 00000 n 0000000113 00001 f 0000168055 00000 n 0000168155 00000 n 0000168312 00000 n 0000168425 00000 n 0000168452 00000 n 0000168483 00000 n 0000168506 00000 n 0000000121 00001 f 0000168593 00000 n 0000168693 00000 n 0000168850 00000 n 0000168963 00000 n 0000168990 00000 n 0000169021 00000 n 0000169044 00000 n 0000000129 00001 f 0000169131 00000 n 0000169231 00000 n 0000169389 00000 n 0000169502 00000 n 0000169529 00000 n 0000169560 00000 n 0000169583 00000 n 0000000137 00001 f 0000169670 00000 n 0000169770 00000 n 0000169928 00000 n 0000170041 00000 n 0000170068 00000 n 0000170099 00000 n 0000170122 00000 n 0000000145 00001 f 0000170209 00000 n 0000170309 00000 n 0000170468 00000 n 0000170581 00000 n 0000170608 00000 n 0000170639 00000 n 0000170662 00000 n 0000000153 00001 f 0000170749 00000 n 0000170849 00000 n 0000171005 00000 n 0000171118 00000 n 0000171145 00000 n 0000171176 00000 n 0000171199 00000 n 0000000161 00001 f 0000171309 00000 n 0000171409 00000 n 0000171566 00000 n 0000171679 00000 n 0000171706 00000 n 0000171737 00000 n 0000171760 00000 n 0000000169 00001 f 0000171883 00000 n 0000171983 00000 n 0000172140 00000 n 0000172253 00000 n 0000172280 00000 n 0000172311 00000 n 0000172334 00000 n 0000000177 00001 f 0000172457 00000 n 0000172557 00000 n 0000172713 00000 n 0000172826 00000 n 0000172853 00000 n 0000172884 00000 n 0000172907 00000 n 0000000185 00001 f 0000173030 00000 n 0000173130 00000 n 0000173289 00000 n 0000173402 00000 n 0000173429 00000 n 0000173460 00000 n 0000173483 00000 n 0000000193 00001 f 0000173606 00000 n 0000173706 00000 n 0000173865 00000 n 0000173978 00000 n 0000174005 00000 n 0000174036 00000 n 0000174059 00000 n 0000000201 00001 f 0000174182 00000 n 0000174282 00000 n 0000174441 00000 n 0000174554 00000 n 0000174581 00000 n 0000174612 00000 n 0000174635 00000 n 0000000209 00001 f 0000174758 00000 n 0000174858 00000 n 0000175015 00000 n 0000175128 00000 n 0000175155 00000 n 0000175186 00000 n 0000175209 00000 n 0000000217 00001 f 0000175332 00000 n 0000175432 00000 n 0000175590 00000 n 0000175703 00000 n 0000175730 00000 n 0000175761 00000 n 0000175784 00000 n 0000000225 00001 f 0000175895 00000 n 0000175995 00000 n 0000176153 00000 n 0000176266 00000 n 0000176293 00000 n 0000176324 00000 n 0000176347 00000 n 0000000233 00001 f 0000176458 00000 n 0000176558 00000 n 0000176716 00000 n 0000176829 00000 n 0000176856 00000 n 0000176887 00000 n 0000176910 00000 n 0000000242 00001 f 0000177021 00000 n 0000177121 00000 n 0000177281 00000 n 0000177394 00000 n 0000177421 00000 n 0000177452 00000 n 0000177475 00000 n 0000177586 00000 n 0000000250 00001 f 0000177672 00000 n 0000177772 00000 n 0000177930 00000 n 0000178043 00000 n 0000178070 00000 n 0000178101 00000 n 0000178124 00000 n 0000000261 00001 f 0000178211 00000 n 0000178311 00000 n 0000178470 00000 n 0000178583 00000 n 0000178610 00000 n 0000178641 00000 n 0000178664 00000 n 0000178775 00000 n 0000179999 00000 n 0000180108 00000 n 0000000269 00001 f 0000180343 00000 n 0000180443 00000 n 0000180599 00000 n 0000180712 00000 n 0000180739 00000 n 0000180770 00000 n 0000180793 00000 n 0000000277 00001 f 0000180880 00000 n 0000180980 00000 n 0000181134 00000 n 0000181247 00000 n 0000181274 00000 n 0000181305 00000 n 0000181328 00000 n 0000000285 00001 f 0000181438 00000 n 0000181538 00000 n 0000181695 00000 n 0000181808 00000 n 0000181835 00000 n 0000181866 00000 n 0000181889 00000 n 0000000293 00001 f 0000181999 00000 n 0000182099 00000 n 0000182258 00000 n 0000182371 00000 n 0000182398 00000 n 0000182429 00000 n 0000182452 00000 n 0000000301 00001 f 0000182539 00000 n 0000182639 00000 n 0000182797 00000 n 0000182910 00000 n 0000182937 00000 n 0000182968 00000 n 0000182991 00000 n 0000000309 00001 f 0000183102 00000 n 0000183202 00000 n 0000183361 00000 n 0000183474 00000 n 0000183501 00000 n 0000183532 00000 n 0000183555 00000 n 0000000317 00001 f 0000183642 00000 n 0000183742 00000 n 0000183901 00000 n 0000184014 00000 n 0000184041 00000 n 0000184072 00000 n 0000184095 00000 n 0000000325 00001 f 0000184182 00000 n 0000184282 00000 n 0000184440 00000 n 0000184553 00000 n 0000184580 00000 n 0000184611 00000 n 0000184634 00000 n 0000000333 00001 f 0000184721 00000 n 0000184821 00000 n 0000184980 00000 n 0000185093 00000 n 0000185120 00000 n 0000185151 00000 n 0000185174 00000 n 0000000341 00001 f 0000185285 00000 n 0000185385 00000 n 0000185544 00000 n 0000185657 00000 n 0000185684 00000 n 0000185715 00000 n 0000185738 00000 n 0000000349 00001 f 0000185849 00000 n 0000185949 00000 n 0000186108 00000 n 0000186221 00000 n 0000186248 00000 n 0000186279 00000 n 0000186302 00000 n 0000000357 00001 f 0000186413 00000 n 0000186513 00000 n 0000186671 00000 n 0000186784 00000 n 0000186811 00000 n 0000186842 00000 n 0000186865 00000 n 0000000365 00001 f 0000186976 00000 n 0000187076 00000 n 0000187235 00000 n 0000187348 00000 n 0000187375 00000 n 0000187406 00000 n 0000187429 00000 n 0000000373 00001 f 0000187516 00000 n 0000187616 00000 n 0000187775 00000 n 0000187888 00000 n 0000187915 00000 n 0000187946 00000 n 0000187969 00000 n 0000000381 00001 f 0000188056 00000 n 0000188156 00000 n 0000188313 00000 n 0000188426 00000 n 0000188453 00000 n 0000188484 00000 n 0000188507 00000 n 0000000389 00001 f 0000188625 00000 n 0000188725 00000 n 0000188883 00000 n 0000188996 00000 n 0000189023 00000 n 0000189054 00000 n 0000189077 00000 n 0000000000 00001 f 0000189195 00000 n 0000189295 00000 n 0000189453 00000 n 0000189566 00000 n 0000189593 00000 n 0000189624 00000 n 0000189647 00000 n 0000189765 00000 n 0000189859 00000 n 0000192465 00000 n 0000192488 00000 n 0000197049 00000 n 0000197072 00000 n 0000208254 00000 n 0000208278 00000 n 0000218773 00000 n 0000218797 00000 n 0000253413 00000 n 0000253437 00000 n trailer << /Size 409 /Info 3 0 R /Root 1 0 R /ID[<174909697a5296eb8594689fc782c55e><0e10b9eeb635850977ac283d5fcdaea2>] >> startxref 256512 %%EOF \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html
new file mode 100644
index 000000000..97f56c6e2
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html
@@ -0,0 +1,11 @@
+<html>
+ <head>
+ <title>Encoding Utilities</title>
+ </head>
+ <body>
+ <p>
+ This package contains a number of utility classes which can come handy
+ from time to time when writing Java code.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java
new file mode 100644
index 000000000..e263eeb59
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java
@@ -0,0 +1,132 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+/**
+ * <p>A {@link RuntimeException} representing a
+ * <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * response for a specified {@link DAVResource}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVException extends RuntimeException {
+
+ private DAVResource resource = null;
+ private int status = 0;
+
+ /**
+ * <p>Create a new {@link DAVException} instance.</p>
+ */
+ public DAVException(int status, String message) {
+ this(status, message, null, null);
+ }
+
+ /**
+ * <p>Create a new {@link DAVException} instance.</p>
+ */
+ public DAVException(int status, String message, Throwable throwable) {
+ this(status, message, throwable, null);
+ }
+
+ /**
+ * <p>Create a new {@link DAVException} instance.</p>
+ */
+ public DAVException(int status, String message, DAVResource resource) {
+ this(status, message, null, resource);
+ }
+
+ /**
+ * <p>Create a new {@link DAVException} instance.</p>
+ */
+ public DAVException(int s, String m, Throwable t, DAVResource r) {
+ super(m, t);
+ this.resource = r;
+ this.status = s;
+ }
+
+ /**
+ * <p>Return the status code associated with this instance.</p>
+ */
+ public int getStatus() {
+ return this.status;
+ }
+
+ /**
+ * <p>Return the {@link DAVResource} associated with this instance.</p>
+ */
+ public DAVResource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * <p>Write the body of this {@link DAVException} to the specified
+ * {@link DAVTransaction}'s output.</p>
+ */
+ public void write(DAVTransaction transaction)
+ throws IOException {
+ transaction.setContentType("text/html; charset=\"UTF-8\"");
+ transaction.setStatus(this.getStatus());
+
+ /* Prepare and log the error message */
+ String message = DAVUtilities.getStatusMessage(this.getStatus());
+ if (message == null) {
+ transaction.setStatus(500);
+ message = Integer.toString(this.getStatus()) + " Unknown";
+ }
+
+ /* Write the error message to the client */
+ PrintWriter out = transaction.write("UTF-8");
+ out.println("<html>");
+ out.print("<head><title>Error ");
+ out.print(message);
+ out.println("</title></head>");
+ out.println("<body>");
+ out.print("<p><b>Error ");
+ out.print(message);
+ out.println("</b></p>");
+
+ /* Check if we have a resource associated with the extension */
+ if (this.getResource() != null) {
+ String r = transaction.lookup(this.getResource()).toASCIIString();
+ out.print("<p>Resource in error: <a href=\"");
+ out.print(r);
+ out.println("\">");
+ out.print(r);
+ out.println("</a></p>");
+ }
+
+ /* Process any exception and its cause */
+ Throwable throwable = this;
+ out.println("<hr /><p>Exception details:</p>");
+ while (throwable != null) {
+ out.print("<pre>");
+ throwable.printStackTrace(out);
+ out.println("</pre>");
+ throwable = throwable.getCause();
+ if (throwable != null) out.println("<hr /><p>Caused by:</p>");
+ }
+
+ /* Close up the HTML */
+ out.println("</body>");
+ out.println("</html>");
+ out.flush();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java
new file mode 100644
index 000000000..6a0976c71
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java
@@ -0,0 +1,165 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * <p>A specialized {@link InputStream} to read from {@link DAVResource}s.</p>
+ *
+ * <p>This specialized {@link InputStream} never throws {@link IOException}s,
+ * but rather relies on the unchecked {@link DAVException} to notify the
+ * framework of the correct DAV errors.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVInputStream extends InputStream {
+
+ /** <p>The {@link InputStream} of the source {@link File}. </p> */
+ protected InputStream input = null;
+ /** <p>The {@link DAVResource} associated with this instance. </p> */
+ private DAVResource resource = null;
+
+ /**
+ * <p>Create a new {@link DAVInputStream} instance.</p>
+ */
+ protected DAVInputStream(DAVResource resource) {
+ if (resource == null) throw new NullPointerException();
+ init(resource);
+ }
+
+ protected void init(DAVResource resource)
+ {
+ try {
+ this.input = new FileInputStream(resource.getFile());
+ } catch (IOException e) {
+ String message = "Unable to read from resource";
+ throw new DAVException (403, message, e, resource);
+ }
+ }
+
+ /**
+ * <p>Read data from this {@link InputStream}.</p>
+ */
+ public int read() {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ return input.read();
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't read data", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Read data from this {@link InputStream}.</p>
+ */
+ public int read(byte b[]) {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ return input.read(b);
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't read data", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Read data from this {@link InputStream}.</p>
+ */
+ public int read(byte b[], int off, int len) {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ return input.read(b, off, len);
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't read data", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Skip a specified amount of data reading from this
+ * {@link InputStream}.</p>
+ */
+ public long skip(long n) {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ return input.skip(n);
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't skip over", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Return the number of bytes that can be read or skipped from this
+ * {@link InputStream} without blocking.</p>
+ */
+ public int available() {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ return input.available();
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't skip over", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Return the number of bytes that can be read or skipped from this
+ * {@link InputStream} without blocking.</p>
+ */
+ public void close() {
+ if (this.input == null) return;
+ try {
+ this.input.close();
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't close", e, this.resource);
+ } finally {
+ this.input = null;
+ }
+ }
+
+ /**
+ * <p>Marks the current position in this {@link InputStream}.</p>
+ */
+ public void mark(int readlimit) {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ this.input.mark(readlimit);
+ }
+
+ /**
+ * <p>Repositions this stream to the position at the time the
+ * {@link #mark(int)} method was last called on this
+ * {@link InputStream}.</p>
+ */
+ public void reset() {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ try {
+ input.reset();
+ } catch (IOException e) {
+ throw new DAVException(403, "Can't reset", e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Tests if this {@link InputStream} supports the {@link #mark(int)}
+ * and {@link #reset()} methods.</p>
+ */
+ public boolean markSupported() {
+ if (this.input == null) throw new IllegalStateException("Closed");
+ return this.input.markSupported();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java
new file mode 100644
index 000000000..6357bcc16
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java
@@ -0,0 +1,46 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+/**
+ * <p>A simple interface identifying a {@link DAVRepository} event listener.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface DAVListener {
+
+ /** <p>An event representing the creation of a collection.</p> */
+ public static final int COLLECTION_CREATED = 1;
+ /** <p>An event representing the deletion of a collection.</p> */
+ public static final int COLLECTION_REMOVED = 2;
+ /** <p>An event representing the creation of a resource.</p> */
+ public static final int RESOURCE_CREATED = 3;
+ /** <p>An event representing the deletion of a resource.</p> */
+ public static final int RESOURCE_REMOVED = 4;
+ /** <p>An event representing the modification of a resource.</p> */
+ public static final int RESOURCE_MODIFIED = 5;
+
+ /**
+ * <p>Notify this {@link DAVListener} of an action occurred on a
+ * specified {@link DAVResource}.</p>
+ *
+ * @param resource the {@link DAVResource} associated with the notification.
+ * @param event a number identifying the type of the notification.
+ */
+ public void notify(DAVResource resource, int event);
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java
new file mode 100644
index 000000000..9ba48466d
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java
@@ -0,0 +1,86 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * <p>A simplicisting class defining an esay way to log stuff to the
+ * {@link ServletContext}.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVLogger {
+
+ private final ServletContext context;
+ private final String servletName;
+ private final boolean debug;
+
+ /**
+ * <p>Create a new {@link DAVLogger} from a {@link ServletConfig}.</p>
+ */
+ public DAVLogger(ServletConfig config, boolean debug) {
+ this.context = config.getServletContext();
+ this.servletName = config.getServletName();
+ this.debug = debug;
+ }
+
+ /**
+ * <p>Log a debug message to the context logger.</p>
+ */
+ public void debug(String message) {
+ if (this.debug) this.doLog(message, null);
+ }
+
+ /**
+ * <p>Log a debug message and related exception to the context logger.</p>
+ */
+ public void debug(String message, Throwable throwable) {
+ if (this.debug) this.doLog(message, throwable);
+ }
+
+ /**
+ * <p>Log a message to the context logger.</p>
+ */
+ public void log(String message) {
+ this.doLog(message, null);
+ }
+
+ /**
+ * <p>Log a message and related exception to the context logger.</p>
+ */
+ public void log(String message, Throwable throwable) {
+ this.doLog(message, throwable);
+ }
+
+ /**
+ * <p>Internal method for formatting messages and logging.</p>
+ */
+ private void doLog(String message, Throwable throwable) {
+ if ((message == null) && (throwable == null)) return;
+ if ((message == null) || ("".equals(message))) message = "No message";
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append('[');
+ buffer.append(this.servletName);
+ buffer.append("] ");
+ buffer.append(message);
+ if (throwable == null) this.context.log(buffer.toString());
+ else this.context.log(buffer.toString(), throwable);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java
new file mode 100644
index 000000000..e73eb52b5
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java
@@ -0,0 +1,41 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.IOException;
+
+
+/**
+ * <p>An interface describing the implementation of a
+ * <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * method.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public interface DAVMethod {
+
+ /**
+ * <p>Process the specified {@link DAVTransaction}.</p>
+ *
+ * @param transaction An object encapsulaing a WebDAV request/response.
+ * @param resource The {@link DAVResource} to process.
+ * @throws IOException If an I/O error occurred.
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException;
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java
new file mode 100644
index 000000000..e7c2fc5da
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java
@@ -0,0 +1,149 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ * <p>A {@link DAVException} representing a
+ * <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>207</code> (Multi-Status) response.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVMultiStatus extends DAVException {
+
+ private Set responses = new HashSet();
+
+ /**
+ * <p>Create a new {@link DAVMultiStatus} instance.</p>
+ */
+ public DAVMultiStatus() {
+ super(207, "Multi-Status response");
+ }
+
+ /**
+ * <p>Write the body of the multi-status response to the specified
+ * {@link DAVTransaction}'s output.</p>
+ */
+ public void write(DAVTransaction transaction)
+ throws IOException {
+ /* What to do on a collection resource */
+ transaction.setStatus(207);
+ transaction.setContentType("text/xml; charset=\"UTF-8\"");
+ PrintWriter out = transaction.write("UTF-8");
+
+ /* Output the XML declaration and the root document tag */
+ out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ out.println("<D:multistatus xmlns:D=\"DAV:\">");
+
+ Iterator responses = this.responses.iterator();
+ while (responses.hasNext()) {
+ Response response = (Response) responses.next();
+ out.println(" <D:response>");
+ out.print(" <D:href>");
+ out.print(transaction.lookup(response.resource));
+ out.println("</D:href>");
+
+ if (response.status != 0) {
+ out.print(" <D:status>HTTP/1.1 ");
+ out.print(DAVUtilities.getStatusMessage(response.status));
+ out.println("</D:status>");
+ }
+
+ if (response.message != null) {
+ out.print(" <D:responsedescription>");
+ out.print(response.message);
+ out.println("</D:responsedescription>");
+ }
+
+ out.println(" </D:response>");
+ }
+
+ out.println("</D:multistatus>");
+ out.flush();
+ }
+
+ /**
+ * <p>Return the number of responses held in this instance.</p>
+ */
+ public int size() {
+ return this.responses.size();
+ }
+
+ /**
+ * <p>Merge the responses held into the specified {@link DAVMultiStatus}
+ * into this instance.</p>
+ */
+ public void merge(DAVMultiStatus multistatus) {
+ if (multistatus == null) return;
+ Iterator iterator = multistatus.responses.iterator();
+ while (iterator.hasNext()) this.responses.add(iterator.next());
+ }
+
+ /**
+ * <p>Merge the details held into the specified {@link DAVException}
+ * into this instance.</p>
+ */
+ public void merge(DAVException exception) {
+ DAVResource resource = exception.getResource();
+ if (resource == null) throw exception;
+
+ int status = exception.getStatus();
+ String message = exception.getMessage();
+ this.responses.add(new Response(resource, status, message));
+ }
+
+ private static class Response implements Comparable {
+ private DAVResource resource = null;
+ private int status = 0;
+ private String message = null;
+
+ public Response(Response response) {
+ this(response.resource, response.status, response.message);
+ }
+
+ public Response(DAVResource resource, int status, String message) {
+ if (resource == null) throw new NullPointerException();
+ this.resource = resource;
+ this.status = status;
+ this.message = message;
+ }
+
+ public int hashCode() {
+ return this.resource.hashCode();
+ }
+
+ public int compareTo(Object object) {
+ Response response = (Response) object;
+ return (this.resource.compareTo(response.resource));
+ }
+
+ public boolean equals(Object object) {
+ if (object instanceof Response) {
+ Response response = (Response) object;
+ return (this.resource.equals(response.resource));
+ }
+ return false;
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java
new file mode 100644
index 000000000..2d6726551
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java
@@ -0,0 +1,56 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.IOException;
+
+/**
+ * <p>A simple {@link DAVException} encapsulating an
+ * <a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a> not modified
+ * response.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVNotModified extends DAVException {
+
+ private DAVResource resource = null;
+
+ /**
+ * <p>Create a new {@link DAVNotModified} instance.</p>
+ */
+ public DAVNotModified(DAVResource resource) {
+ super(304, "Resource Not Modified");
+ this.resource = resource;
+ }
+
+ /**
+ * <p>Write the body of this {@link DAVNotModified} to the specified
+ * {@link DAVTransaction}'s output.</p>
+ */
+ public void write(DAVTransaction transaction)
+ throws IOException {
+ transaction.setStatus(this.getStatus());
+
+ /* Figure out what we're dealing with here */
+ String etag = resource.getEntityTag();
+ String lmod = DAVUtilities.formatHttpDate(resource.getLastModified());
+
+ /* Set the normal headers that are required for a GET */
+ if (etag != null) transaction.setHeader("ETag", etag);
+ if (lmod != null) transaction.setHeader("Last-Modified", lmod);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java
new file mode 100644
index 000000000..6a5c80601
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java
@@ -0,0 +1,187 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * <p>A specialized {@link OutputStream} to write to {@link DAVResource}s.</p>
+ *
+ * <p>When writing to this {@link OutputStream} the data will be written to
+ * a temporary file. This temporary file will be moved to its final destination
+ * (the original file identifying the resource) when the {@link #close()}
+ * method is called.</p>
+ *
+ * <p>This specialized {@link OutputStream} never throws {@link IOException}s,
+ * but rather relies on the unchecked {@link DAVException} to notify the
+ * framework of the correct DAV errors.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVOutputStream extends OutputStream {
+
+ /** <p>The original resource {@link File}.</p> */
+ private File temporary = null;
+ /** <p>The {@link OutputStream} of the temporary {@link File}. </p> */
+ protected OutputStream output = null;
+ /** <p>The {@link DAVResource} associated with this instance. </p> */
+ private DAVResource resource = null;
+
+ /**
+ * <p>Create a new {@link DAVOutputStream} instance.</p>
+ */
+ protected DAVOutputStream(DAVResource resource) {
+ if (resource == null) throw new NullPointerException();
+ this.resource = resource;
+ init(resource);
+ }
+
+ protected void init(DAVResource resource) {
+ try {
+ this.temporary = resource.getParent().getFile();
+ this.temporary = File.createTempFile(DAVResource.PREFIX,
+ DAVResource.SUFFIX,
+ this.temporary);
+ this.output = new FileOutputStream(this.temporary);
+ } catch (IOException e) {
+ String message = "Unable to create temporary file";
+ throw new DAVException(507, message, e, resource);
+ }
+ }
+
+ /**
+ * <p>Rename the temporary {@link File} to the original one.</p>
+ */
+ protected void rename(File temporary, File original)
+ throws IOException {
+ if ((original.exists()) && (!original.delete())) {
+ throw new IOException("Unable to delete original file");
+ }
+ if (!temporary.renameTo(original)) {
+ throw new IOException("Unable to rename temporary file");
+ }
+ }
+
+ /**
+ * <p>Abort any data written to the temporary file and delete it.</p>
+ */
+ public void abort() {
+ if (this.temporary.exists()) this.temporary.delete();
+ if (this.output != null) try {
+ this.output.close();
+ } catch (IOException exception) {
+ // Swallow the IOException on close
+ } finally {
+ this.output = null;
+ }
+ }
+
+ /**
+ * <p>Close this {@link OutputStream} {@link #rename(File,File) renaming}
+ * the temporary file to the {@link DAVResource#getFile() original} one.</p>
+ */
+ public void close() {
+ if (this.output == null) return;
+ try {
+ /* What kind of event should this invocation trigger? */
+ int event = this.resource.getFile().exists() ?
+ DAVListener.RESOURCE_MODIFIED:
+ DAVListener.RESOURCE_CREATED;
+
+ /* Make sure that everything is closed and named properly */
+ this.output.close();
+ this.output = null;
+ this.rename(this.temporary, this.resource.getFile());
+
+ /* Send notifications to all listeners of the repository */
+ this.resource.getRepository().notify(this.resource, event);
+
+ } catch (IOException e) {
+ String message = "Error processing temporary file";
+ throw new DAVException(507, message, e, this.resource);
+ } finally {
+ this.abort();
+ }
+ }
+
+ /**
+ * <p>Flush any unwritten data to the disk.</p>
+ */
+ public void flush() {
+ if (this.output == null) throw new IllegalStateException("Closed");
+ try {
+ this.output.flush();
+ } catch (IOException e) {
+ this.abort();
+ String message = "Unable to flush buffers";
+ throw new DAVException(507, message, e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Write data to this {@link OutputStream}.</p>
+ */
+ public void write(int b) {
+ if (this.output == null) throw new IllegalStateException("Closed");
+ try {
+ this.output.write(b);
+ } catch (IOException e) {
+ this.abort();
+ String message = "Unable to write data";
+ throw new DAVException(507, message, e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Write data to this {@link OutputStream}.</p>
+ */
+ public void write(byte b[]) {
+ if (this.output == null) throw new IllegalStateException("Closed");
+ try {
+ this.output.write(b);
+ } catch (IOException e) {
+ this.abort();
+ String message = "Unable to write data";
+ throw new DAVException(507, message, e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Write data to this {@link OutputStream}.</p>
+ */
+ public void write(byte b[], int o, int l) {
+ if (this.output == null) throw new IllegalStateException("Closed");
+ try {
+ this.output.write(b, o, l);
+ } catch (IOException e) {
+ this.abort();
+ String message = "Unable to write data";
+ throw new DAVException(507, message, e, this.resource);
+ }
+ }
+
+ /**
+ * <p>Finalize this {@link DAVOutputStream} instance.</p>
+ */
+ public void finalize() {
+ this.abort();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java
new file mode 100644
index 000000000..d50b65875
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java
@@ -0,0 +1,92 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * <p>The <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * transactions processor.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVProcessor {
+
+ /** <p>All the implemented methods, comma separated.</p> */
+ public static final String METHODS = "COPY,DELETE,GET,HEAD,MKCOL,MOVE," +
+ "OPTIONS,PROPFIND,PROPPATCH,PUT";
+
+ /** <p>A static map of all known webdav methods.</p> */
+ private static Map INSTANCES = new HashMap();
+ static {
+ /* Load and verify all the known methods */
+ final String thisName = DAVProcessor.class.getName();
+ final int packageDelimiter = thisName.lastIndexOf('.');
+ final String packageName = packageDelimiter < 1 ? "methods." :
+ thisName.substring(0, packageDelimiter) + ".methods.";
+ final StringTokenizer tokenizer = new StringTokenizer(METHODS, ",");
+ final ClassLoader classLoader = DAVProcessor.class.getClassLoader();
+ while (tokenizer.hasMoreTokens()) try {
+ final String method = tokenizer.nextToken();
+ final String className = packageName + method;
+ final Class clazz = classLoader.loadClass(className);
+ INSTANCES.put(method, (DAVMethod) clazz.newInstance());
+ } catch (Throwable throwable) {
+ InternalError error = new InternalError("Error loading method");
+ throw (InternalError) error.initCause(throwable);
+ }
+ }
+
+ /** <p>The {@link DAVRepository} associated with this instance.</p> */
+ private DAVRepository repository = null;
+
+ /**
+ * <p>Create a new {@link DAVProcessor} instance.</p>
+ */
+ public DAVProcessor(DAVRepository repository) {
+ if (repository == null) throw new NullPointerException();
+ this.repository = repository;
+ }
+
+ /**
+ * <p>Process the specified {@link DAVTransaction} fully.</p>
+ */
+ public void process(DAVTransaction transaction)
+ throws IOException {
+ try {
+ String method = transaction.getMethod();
+ if (INSTANCES.containsKey(method)) {
+ String path = transaction.getNormalizedPath();
+ DAVResource resource = this.repository.getResource(path);
+ DAVMethod instance = ((DAVMethod) INSTANCES.get(method));
+ instance.process(transaction, resource);
+ } else {
+ String message = "Method \"" + method + "\" not implemented";
+ throw new DAVException(501, message);
+ }
+ } catch (DAVException exception) {
+ exception.write(transaction);
+ }
+ }
+
+ public void setMethod( String methodKey, DAVMethod method ) {
+ INSTANCES.put( methodKey, method );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java
new file mode 100644
index 000000000..aa3ed42d4
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java
@@ -0,0 +1,164 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * <p>A simple class representing a {@link File} based WebDAV repository.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVRepository {
+
+ /** <p>A {@link String} of all acceptable characters in a URI.</p> */
+ private static final String ACCEPTABLE =
+ "ABCDEFGHIJLKMNOPQRSTUVWXYZ" + // ALPHA (UPPER)
+ "abcdefghijklmnopqrstuvwxyz" + // ALPHA (LOWER)
+ "0123456789" + // DIGIT
+ "_-!.~'()*" + // UNRESERVED
+ ",;:$&+=" + // PUNCT
+ "?/[]@"; // RESERVED
+
+
+ /** <p>The {@link File} identifying the root of this repository.</p> */
+ protected File root = null;
+ /** <p>The {@link URI} associated with the root of this repository.</p> */
+ protected URI base = null;
+ /** <p>The {@link Set} of all configured {@link DAVListener}s.</p> */
+ private Set listeners = new HashSet();
+
+ /**
+ * <p>Create a new {@link DAVRepository} instance.</p>
+ *
+ * @param root The {@link File} identifying the root of the repository.
+ * @throws IOException If the specified root is not a directory.
+ * @throws NullPointerExceptoin If the specified root was <b>null</b>.
+ */
+ public DAVRepository(File root)
+ throws IOException {
+ init(root);
+ }
+
+ protected void init(File root)
+ throws IOException {
+ if (root == null) throw new NullPointerException("Null root");
+ if (root.isDirectory()) {
+ this.root = root.getCanonicalFile();
+ this.base = this.root.toURI().normalize();
+ } else {
+ throw new IOException("Root \"" + root + "\" is not a directory");
+ }
+ }
+
+ /**
+ * <p>Return the {@link URI} representing the root directory of this
+ * {@link DAVRepository}.</p>
+ *
+ * @return a <b>non-null</b> {@link URI} instance.
+ */
+ protected URI getRepositoryURI() {
+ return (this.base);
+ }
+
+ /**
+ * <p>Return the {@link DAVResource} associated with the given name.</p>
+ *
+ * @param name a {@link String} identifying the resource name.
+ * @return a <b>non-null</b> {@link DAVResource} instance.
+ * @throws IOException If the resource could not be resolved.
+ */
+ public DAVResource getResource(String name)
+ throws IOException {
+ if (name == null) return this.getResource((URI) null);
+
+ try {
+ /* Encode the string into a URI */
+ StringBuffer buffer = new StringBuffer();
+ byte encoded[] = name.getBytes("UTF-8");
+ for (int x = 0; x < encoded.length; x ++) {
+ if (ACCEPTABLE.indexOf((int)encoded[x]) < 0) {
+ buffer.append('%');
+ buffer.append(DAVUtilities.toHexString(encoded[x]));
+ continue;
+ }
+ buffer.append((char) encoded[x]);
+ }
+
+ return this.getResource(new URI(buffer.toString()));
+ } catch (URISyntaxException exception) {
+ String message = "Invalid resource name \"" + name + "\"";
+ throw (IOException) new IOException(message).initCause(exception);
+ }
+ }
+
+ /**
+ * <p>Return the {@link DAVResource} associated with a {@link URI}.</p>
+ *
+ * <p>If the specified {@link URI} is relative it will be resolved against
+ * the root of this {@link DAVRepository}.</p>
+ *
+ * @param uri an absolute or relative {@link URI} identifying the resource.
+ * @return a <b>non-null</b> {@link DAVResource} instance.
+ * @throws IOException If the resource could not be resolved.
+ */
+ public DAVResource getResource(URI uri)
+ throws IOException {
+ if (uri == null) return new DAVResource(this, this.root);
+
+ if (! uri.isAbsolute()) uri = this.base.resolve(uri).normalize();
+ return new DAVResource(this, new File(uri).getAbsoluteFile());
+ }
+
+ /**
+ * <p>Add a new {@link DAVListener} to the list of instances notified by
+ * this {@link DAVRepository}.</p>
+ */
+ public void addListener(DAVListener listener) {
+ if (listener != null) this.listeners.add(listener);
+ }
+
+ /**
+ * <p>Remove a {@link DAVListener} from the list of instances notified by
+ * this {@link DAVRepository}.</p>
+ */
+ public void removeListener(DAVListener listener) {
+ if (listener != null) this.listeners.remove(listener);
+ }
+
+ /**
+ * <p>Notify all configured {@link DAVListener}s of an event.</p>
+ */
+ protected void notify(DAVResource resource, int event) {
+ if (resource == null) throw new NullPointerException("Null resource");
+ if (resource.getRepository() != this)
+ throw new IllegalArgumentException("Invalid resource");
+
+ Iterator iterator = this.listeners.iterator();
+ while (iterator.hasNext()) try {
+ ((DAVListener)iterator.next()).notify(resource, event);
+ } catch (RuntimeException exception) {
+ // Swallow any RuntimeException thrown by listeners.
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java
new file mode 100644
index 000000000..f2d0911ee
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java
@@ -0,0 +1,514 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * <p>A simple representation of a WebDAV resource based on {@link File}s.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVResource implements Comparable {
+
+ /** <p>The mime type when {@link #isCollection()} is <b>true</b>.</p> */
+ public static final String COLLECTION_MIME_TYPE = "httpd/unix-directory";
+
+ /** <p>The prefix for all temporary resources.</p> */
+ protected static final String PREFIX = ".dav_";
+ /** <p>The suffix for all temporary resources.</p> */
+ protected static final String SUFFIX = ".temp";
+ /** <p>The {@link DAVRepository} instance containing this resource.</p> */
+ private DAVRepository repository = null;
+ /** <p>The {@link File} associated with this resource.</p> */
+ private File file = null;
+
+ /* ====================================================================== */
+ /* Constructors */
+ /* ====================================================================== */
+
+ /**
+ * <p>Create a new {@link DAVResource} instance.</p>
+ */
+ protected DAVResource(DAVRepository repo, File file) {
+ if (repo == null) throw new NullPointerException("Null repository");
+ if (file == null) throw new NullPointerException("Null resource");
+ init(repo, file);
+ }
+
+ protected void init(DAVRepository repo, File file)
+ {
+ this.repository = repo;
+ this.file = file;
+
+ if (this.getRelativeURI().isAbsolute())
+ throw new DAVException(412, "Error relativizing resource");
+ }
+
+ /* ====================================================================== */
+ /* Generic object methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return an integer number for the hash value of this instance.</p>
+ */
+ public int hashCode() {
+ return this.file.hashCode();
+ }
+
+ /**
+ * <p>Compare this instance to another object for equality.</p>
+ */
+ public boolean equals(Object object) {
+ if (object == null) return (false);
+ if (object instanceof DAVResource) {
+ DAVResource resource = (DAVResource) object;
+ boolean u = this.file.equals(resource.file);
+ boolean r = this.repository == resource.repository;
+ return (u && r);
+ } else {
+ return (false);
+ }
+ }
+
+ /**
+ * <p>Compare this instance to another object for sorting.</p>
+ */
+ public int compareTo(Object object) {
+ DAVResource resource = (DAVResource) object;
+ return (this.file.compareTo(resource.file));
+ }
+
+ /* ====================================================================== */
+ /* Resource checkers */
+ /* ====================================================================== */
+
+ /**
+ * <p>Checks if this {@link DAVResource} is a null (non existant) one.</p>
+ *
+ * @return <b>true</b> if this resource does not esist (is a null resource).
+ */
+ public boolean isNull() {
+ return (! this.file.exists());
+ }
+
+ /**
+ * <p>Checks if this {@link DAVResource} is a collection.</p>
+ *
+ * @return <b>true</b> if this resource is a collection.
+ */
+ public boolean isCollection() {
+ if (this.isNull()) return false;
+ return (this.file.isDirectory());
+ }
+
+ /**
+ * <p>Checks if this {@link DAVResource} is an existing resource.</p>
+ *
+ * @return <b>true</b> if this resource is a collection.
+ */
+ public boolean isResource() {
+ if (this.isNull()) return false;
+ return (! this.isCollection());
+ }
+
+ /* ====================================================================== */
+ /* Resource methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the {@link File} associated with this resource.</p>
+ */
+ protected File getFile() {
+ return this.file;
+ }
+
+ /**
+ * <p>Return the {@link DAVRepository} associated with this resource.</p>
+ */
+ public DAVRepository getRepository() {
+ return this.repository;
+ }
+
+ /**
+ * <p>Return the bare name of this resource (without any &quot;/&quot;
+ * slashes at the end if it is a collection).</p>
+ *
+ * @return a <b>non null</b> {@link String}.
+ */
+ public String getName() {
+ return this.file.getName();
+ }
+
+ /**
+ * <p>Return the display name of this resource (with an added &quot;/&quot;
+ * slash at the end if it is a collection).</p>
+ *
+ * @return a <b>non null</b> {@link String}.
+ */
+ public String getDisplayName() {
+ String name = this.getName();
+ if (this.isCollection()) return (name + "/");
+ return name;
+ }
+
+ /**
+ * <p>Return the path of this {@link DAVResource} relative to the root
+ * of the associated {@link DAVRepository}.</p>
+ *
+ * @return a <b>non null</b> {@link String}.
+ */
+ public String getRelativePath() {
+ return this.getRelativeURI().toASCIIString();
+ }
+
+ /**
+ * <p>Return the {@link URI} of this {@link DAVResource} relative to the
+ * root of the associated {@link DAVRepository}.</p>
+ *
+ * @return a <b>non-null</b> {@link URI} instance.
+ */
+ public URI getRelativeURI() {
+ URI uri = this.file.toURI();
+ return this.repository.getRepositoryURI().relativize(uri).normalize();
+ }
+
+ /**
+ * <p>Return the parent {@link DAVResource} of this instance.</p>
+ *
+ * @return a <b>non-null</b> {@link DAVResource} instance or <b>null</b>
+ * if this {@link DAVResource} is the repository root.
+ */
+ public DAVResource getParent() {
+ try {
+ return new DAVResource(this.repository, this.file.getParentFile());
+ } catch (Throwable throwable) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Return an {@link Iterator} over all children of this instance.</p>
+ *
+ * @return a <b>non-null</b> {@link Iterator} instance or <b>null</b> if
+ * this {@link DAVResource} is not a collection.
+ * @throws IOException If the resource could not be resolved.
+ */
+ public Iterator getChildren() {
+ if (! this.isCollection()) return null;
+
+ File children[] = this.file.listFiles();
+ if (children == null) children = new File[0];
+ List resources = new ArrayList(children.length);
+
+ for (int x = 0; x < children.length; x++) {
+ String c = children[x].getName();
+ if (c.startsWith(PREFIX) && c.endsWith(SUFFIX)) continue;
+ resources.add(new DAVResource(this.repository, children[x]));
+ }
+
+ return resources.iterator();
+ }
+
+ /* ====================================================================== */
+ /* DAV Properties */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the MIME Content-Type of this {@link DAVResource}.</p>
+ *
+ * <p>If the {@link #isCollection()} method returns <b>true</b> this
+ * method always returns <code>text/html</code>.</p>
+ *
+ * @return a {@link String} instance or <b>null</b> if this resource does
+ * not exist.
+ */
+ public String getContentType() {
+ if (this.isNull()) return null;
+ if (this.isCollection()) return COLLECTION_MIME_TYPE;
+ return DAVUtilities.getMimeType(this.getDisplayName());
+ }
+
+ /**
+ * <p>Return the MIME Content-Length of this {@link DAVResource}.</p>
+ *
+ * @return a {@link Long} instance or <b>null</b> if this resource does
+ * not exist or is a collection.
+ */
+ public Long getContentLength() {
+ if (this.isNull() || this.isCollection()) return null;
+ return new Long(this.file.length());
+ }
+
+ /**
+ * <p>Return the creation date of this {@link DAVResource}.</p>
+ *
+ * <p>As this implementation relies on a {@link File} backend, this method
+ * will always return the same as {@link #getLastModified()}.</p>
+ *
+ * @return a {@link String} instance or <b>null</b> if this resource does
+ * not exist.
+ */
+ public Date getCreationDate() {
+ if (this.isNull()) return null;
+ return new Date(this.file.lastModified());
+ }
+
+ /**
+ * <p>Return the last modification date of this {@link DAVResource}.</p>
+ *
+ * @return a {@link String} instance or <b>null</b> if this resource does
+ * not exist.
+ */
+ public Date getLastModified() {
+ if (this.isNull()) return null;
+ return new Date(this.file.lastModified());
+ }
+
+ /**
+ * <p>Return a {@link String} representing the Entity Tag of this
+ * {@link DAVResource} as described by the
+ * <a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP RFC</a>.</p>
+ *
+ * @return a {@link String} instance or <b>null</b> if this resource does
+ * not exist.
+ */
+ public String getEntityTag() {
+ if (this.isNull()) return null;
+
+ String path = this.getRelativePath();
+ StringBuffer etag = new StringBuffer();
+ etag.append('"');
+
+ /* Append the MD5 hash of this resource name */
+ try {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ digester.reset();
+ digester.update(path.getBytes("UTF8"));
+ etag.append(DAVUtilities.toHexString(digester.digest()));
+ etag.append('-');
+ } catch (Exception e) {
+ // If we can't get the MD5 HASH, let's ignore and hope...
+ }
+
+ /* Append the hashCode of this resource name */
+ etag.append(DAVUtilities.toHexString(path.hashCode()));
+
+ /* Append the last modification date if possible */
+ Date date = this.getLastModified();
+ if (date != null) {
+ etag.append('-');
+ etag.append(DAVUtilities.toHexString(date.getTime()));
+ }
+
+ /* Close the ETag */
+ etag.append('"');
+ return(etag.toString());
+ }
+
+ /* ====================================================================== */
+ /* DAV Operations */
+ /* ====================================================================== */
+
+ /**
+ * <p>Delete this resource.</p>
+ *
+ * @throws DAVException If for any reason this resource cannot be deleted.
+ */
+ public void delete()
+ throws DAVMultiStatus {
+ if (this.isNull()) throw new DAVException(404, "Not found", this);
+
+ if (this.isResource()) {
+ if (!windowsSafeDelete(this.file)) {
+ throw new DAVException(403, "Can't delete resource", this);
+ } else {
+ this.repository.notify(this, DAVListener.RESOURCE_REMOVED);
+ }
+ } else if (this.isCollection()) {
+ DAVMultiStatus multistatus = new DAVMultiStatus();
+
+ Iterator children = this.getChildren();
+ while (children.hasNext()) try {
+ ((DAVResource)children.next()).delete();
+ } catch (DAVException exception) {
+ multistatus.merge(exception);
+ }
+
+ if (multistatus.size() > 0) throw multistatus;
+ if (!this.file.delete()) {
+ throw new DAVException(403, "Can't delete collection", this);
+ } else {
+ this.repository.notify(this, DAVListener.COLLECTION_REMOVED);
+ }
+ }
+ }
+
+ /**
+ * <p>Copy this resource to the specified destination.</p>
+ *
+ * @throws DAVException If for any reason this resource cannot be deleted.
+ */
+ public void copy(DAVResource dest, boolean overwrite, boolean recursive)
+ throws DAVMultiStatus {
+
+ /*
+ * NOTE: Since the COPY operation relies on other operation defined in
+ * this class (and in DAVOutputStream for resources) rather than on
+ * files temselves, notifications are sent elsewhere, not here.
+ */
+
+ if (this.isNull()) throw new DAVException(404, "Not found", this);
+
+ /* Check if the destination exists and delete if possible */
+ if (!dest.isNull()) {
+ if (! overwrite) {
+ String msg = "Not overwriting existing destination";
+ throw new DAVException(412, msg, dest);
+ }
+ dest.delete();
+ }
+
+ /* Copy a single resource (destination is null as we deleted it) */
+ if (this.isResource()) {
+ DAVInputStream in = this.read();
+ DAVOutputStream out = dest.write();
+ byte buffer[] = new byte[4096];
+ int k = -1;
+ while ((k = in.read(buffer)) != -1) out.write(buffer, 0, k);
+ in.close();
+ out.close();
+ }
+
+ /* Copy the collection and all nested members */
+ if (this.isCollection()) {
+ dest.makeCollection();
+ if (! recursive) return;
+
+ DAVMultiStatus multistatus = new DAVMultiStatus();
+ Iterator children = this.getChildren();
+ while (children.hasNext()) try {
+ DAVResource childResource = (DAVResource) children.next();
+ File child = new File(dest.file, childResource.file.getName());
+ DAVResource target = new DAVResource(this.repository, child);
+ childResource.copy(target, overwrite, recursive);
+ } catch (DAVException exception) {
+ multistatus.merge(exception);
+ }
+ if (multistatus.size() > 0) throw multistatus;
+ }
+ }
+
+ /**
+ * <p>Moves this resource to the specified destination.</p>
+ *
+ * @throws DAVException If for any reason this resource cannot be deleted.
+ */
+ public void move(DAVResource dest, boolean overwrite, boolean recursive)
+ throws DAVMultiStatus {
+ // the base class implementation is just copy-then-delete
+ copy(dest, overwrite, recursive);
+ this.delete();
+ }
+
+ /**
+ * <p>Create a collection identified by this {@link DAVResource}.</p>
+ *
+ * <p>This resource must be {@link #isNull() non-null} and its
+ * {@link #getParent() parent} must be accessible and be a
+ * {@link #isCollection() collection}.</p>
+ *
+ * @throws DAVException If for any reason a collection identified by this
+ * resource cannot be created.
+ */
+ public void makeCollection() {
+ DAVResource parent = this.getParent();
+ if (!this.isNull())
+ throw new DAVException(405, "Resource exists", this);
+ if (parent.isNull())
+ throw new DAVException(409, "Parent does not not exist", this);
+ if (!parent.isCollection())
+ throw new DAVException(403, "Parent not a collection", this);
+ if (!this.file.mkdir())
+ throw new DAVException(507, "Can't create collection", this);
+ this.repository.notify(this, DAVListener.COLLECTION_CREATED);
+ }
+
+ /**
+ * <p>Return an {@link InputStream} reading the resource.</p>
+ *
+ * @return a <b>non-null</b> {@link InputStream} instance.
+ */
+ public DAVInputStream read() {
+ if (this.isNull()) throw new DAVException(404, "Not found", this);
+ if (this.isCollection())
+ throw new DAVException (403, "Resource is collection", this);
+ return new DAVInputStream(this);
+ }
+
+ /**
+ * <p>Return a {@link DAVOutputStream} writing to this {@link DAVResource}
+ * instance.</p>
+ *
+ * @return a <b>non-null</b> {@link DAVOutputStream} instance.
+ */
+ public DAVOutputStream write() {
+ DAVResource parent = this.getParent();
+ if (this.isCollection())
+ throw new DAVException(409, "Can't write a collection", this);
+ if (parent.isNull())
+ throw new DAVException(409, "Parent doesn't exist", this);
+ if (! parent.isCollection())
+ throw new DAVException(403, "Parent not a collection", this);
+ return new DAVOutputStream(this);
+ }
+
+ /** File.delete(file) sometimes fails transiently on Windows.
+ * This occurs even in low-I/O conditions, with file Explorer closed.
+ * Delete can still fail (correctly) due to the separate Windows problem
+ * of file sharing violations.
+ * @return the status of the last attempt of File.delete()
+ */
+ private static boolean windowsSafeDelete(File f)
+ {
+ // www.mail-archive.com/java-user@lucene.apache.org/msg08994.html
+ boolean success = f.delete();
+ int attempts = 1;
+ while(!success && f.exists() && attempts < 3) {
+ if(attempts > 2) {
+ System.gc();
+ }
+ try {
+ Thread.sleep(20);
+ } catch (InterruptedException ignore) {
+ }
+ success = f.delete();
+ attempts++;
+ }
+ return success;
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java
new file mode 100644
index 000000000..de36a6f6c
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java
@@ -0,0 +1,280 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ * <p>A very simple servlet capable of processing very simple
+ * <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * requests.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVServlet implements Servlet, DAVListener {
+
+ /** <p>The {@link DAVRepository} configured for this instance.</p> */
+ protected DAVRepository repository = null;
+ /** <p>The {@link DAVLogger} configured for this instance.</p> */
+ protected DAVLogger logger = null;
+ /** <p>The {@link DAVProcessor} configured for this instance.</p> */
+ protected DAVProcessor processor = null;
+ /** <p>The {@link ServletContext} associated with this instance.</p> */
+ private ServletContext context = null;
+ /** <p>The {@link ServletConfig} associated with this instance.</p> */
+ private ServletConfig config= null;
+
+ /**
+ * <p>Create a new {@link DAVServlet} instance.</p>
+ */
+ public DAVServlet() {
+ super();
+ }
+
+ /**
+ * <p>Initialize this {@link Servlet} instance.</p>
+ *
+ * <p>The only initialization parameter required by this servlet is the
+ * &quot;<code>rootPath</code>&quot; parameter specifying the path
+ * of the repository root (either absolute or relative to the configured
+ * {@link ServletContext}.</p>
+ *
+ * <p>If the specified root is relative, it will be considered to
+ * be relative to the {@link ServletContext} deployment path.</p>
+ *
+ * <p>In any case, the specified root must ultimately point to an existing
+ * directory on a locally-accessible file system.</p>
+ *
+ * <p>When set to <code>true</code>, an optional parameter called
+ * <code>xmlOnly</code> will force this {@link DAVServlet} to use an
+ * {@link XMLRepository} instead of the default {@link DAVRepository}.</p>
+ *
+ * <p>Finally, when set to <code>true</code>, the optional parameter
+ * <code>debugEnabled</code> will enable logging of method invocation and
+ * events in the repository.</p>
+ */
+ public void init(ServletConfig config)
+ throws ServletException {
+ /* Remember the configuration instance */
+ this.config = config;
+ this.context = config.getServletContext();
+
+ /* Setup logging */
+ boolean debug = "true".equals(config.getInitParameter("debugEnabled"));
+ this.logger = new DAVLogger(config, debug);
+
+ /* Try to retrieve the WebDAV root path from the configuration */
+ String rootPath = config.getInitParameter("rootPath");
+ if (rootPath == null)
+ throw new ServletException("Parameter \"rootPath\" not specified");
+
+ /* Create repository and processor */
+ try {
+ File root = new File(rootPath);
+ // The repository may not be the local filesystem. It may be rooted at "/".
+ // But then on Windows new File("/").isAbsolute() is false.
+ boolean unixAbsolute = rootPath.startsWith("/");
+ boolean localAbsolute = root.isAbsolute();
+ if (! unixAbsolute && !localAbsolute) {
+ URL url = this.context.getResource("/" + rootPath);
+ if (! "file".equals(url.getProtocol())) {
+ throw new ServletException("Invalid root \"" + url + "\"");
+ } else {
+ root = new File(url.getPath());
+ }
+ }
+
+ /* Discover the repository implementation at runtime */
+ String repositoryClass = config.getInitParameter("repositoryClass");
+ if(repositoryClass != null) {
+ this.repository = DAVServlet.newRepository(repositoryClass, root);
+ } else {
+ // legacy configuration format. keep for now
+ /* Make sure that we use the correct repository type */
+ if ("true".equalsIgnoreCase(config.getInitParameter("xmlOnly"))) {
+ this.repository = new XMLRepository(root);
+ } else {
+ this.repository = new DAVRepository(root);
+ }
+ }
+
+ /* Initialize the processor and register ourselves as listeners */
+ this.processor = new DAVProcessor(this.repository);
+ this.repository.addListener(this);
+ this.logger.log("Initialized from " + root.getPath());
+
+ } catch (MalformedURLException e) {
+ throw new ServletException("Can't resolve \"" + rootPath + "\"", e);
+ } catch (IOException e) {
+ String msg = "Can't initialize repository at \"" + rootPath + "\"";
+ throw new ServletException(msg, e);
+ }
+
+ /* Finally, register this repository in the servlet context */
+ final String key = getRepositoryKey(config.getServletName());
+ this.context.setAttribute(key, this.repository);
+ }
+
+ /**
+ * <p>Retrieve a {@link DAVRepository} for a given {@link File}.</p>
+ */
+ public DAVRepository getRepository(File root)
+ throws IOException {
+ return new XMLRepository(root);
+ }
+
+ /**
+ * <p>Detroy this {@link Servlet} instance.</p>
+ */
+ public void destroy() {
+ this.repository.removeListener(this);
+ }
+
+ /**
+ * <p>Return the {@link ServletConfig} associated with this instance.</p>
+ */
+ public ServletConfig getServletConfig() {
+ return (this.config);
+ }
+
+ /**
+ * <p>Return the {@link ServletContext} associated with this instance.</p>
+ */
+ public ServletContext getServletContext() {
+ return (this.context);
+ }
+
+ /**
+ * <p>Return a informative {@link String} about this servlet.</p>
+ */
+ public String getServletInfo() {
+ return DAVUtilities.SERVLET_INFORMATION;
+ }
+
+ /**
+ * <p>Execute the current request.</p>
+ */
+ public void service(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse res = (HttpServletResponse) response;
+
+ /* Mark our presence */
+ res.setHeader("Server", this.context.getServerInfo() + ' ' +
+ DAVUtilities.SERVLET_SIGNATURE);
+
+ /* Normal methods are processed by their individual instances */
+ DAVTransaction transaction = new DAVTransaction(req, res);
+ try {
+ this.processor.process(transaction);
+ } catch (RuntimeException exception) {
+ final String header = req.getMethod() + ' ' + req.getRequestURI()
+ + ' ' + req.getProtocol();
+ this.context.log("Error processing: " + header);
+ this.context.log("Exception processing DAV transaction", exception);
+ throw exception;
+ }
+ }
+
+ /* ====================================================================== */
+ /* DAV LISTENER INTERFACE IMPLEMENTATION */
+ /* ====================================================================== */
+
+ /**
+ * <p>Receive notification of an event occurred in a specific
+ * {@link DAVRepository}.</p>
+ */
+ public void notify(DAVResource resource, int event) {
+ String message = "Unknown event";
+ switch (event) {
+ case DAVListener.COLLECTION_CREATED:
+ message = "Collection created";
+ break;
+ case DAVListener.COLLECTION_REMOVED:
+ message = "Collection removed";
+ break;
+ case DAVListener.RESOURCE_CREATED:
+ message = "Resource created";
+ break;
+ case DAVListener.RESOURCE_REMOVED:
+ message = "Resource removed";
+ break;
+ case DAVListener.RESOURCE_MODIFIED:
+ message = "Resource modified";
+ break;
+ }
+ this.logger.debug(message + ": \"" + resource.getRelativePath() + "\"");
+ }
+
+ /* ====================================================================== */
+ /* CONTEXT METHODS */
+ /* ====================================================================== */
+
+ /**
+ * <p>Retrieve the key in the {@link ServletContext} where the instance of
+ * the {@link DAVRepository} associated with a named {@link DAVServlet}
+ * can be found.</p>
+ *
+ * @param servletName the name of the {@link DAVServlet} as specified in
+ * the <code>web.xml</code> deployment descriptor.</p>
+ */
+ public static String getRepositoryKey(String servletName) {
+ if (servletName == null) throw new NullPointerException();
+ return DAVRepository.class.getName() + "." + servletName;
+ }
+
+ /** factory for subclasses configured in web.xml
+ * @param repositoryClass must extend DAVRepository and have a public constructor(File).
+ * */
+ static DAVRepository newRepository(String repositoryClass, File root)
+ throws ServletException
+ {
+ try {
+ Class c = Class.forName(repositoryClass);
+ Constructor ctor = c.getConstructor(new Class[]{File.class});
+ DAVRepository repo = (DAVRepository)ctor.newInstance(new Object[]{root});
+ return repo;
+ } catch(ClassNotFoundException e) {
+ throw new ServletException(e);
+ } catch(LinkageError le) {
+ throw new ServletException(le);
+ } catch(NoSuchMethodException ns) {
+ throw new ServletException(ns);
+ } catch(InvocationTargetException it) {
+ throw new ServletException(it);
+ } catch(IllegalAccessException ia) {
+ throw new ServletException(ia);
+ } catch(InstantiationException ie) {
+ throw new ServletException(ie);
+ }
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java
new file mode 100644
index 000000000..20089f022
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java
@@ -0,0 +1,280 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
+
+
+/**
+ * <p>A simple wrapper isolating the Java Servlet API from this
+ * <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVTransaction {
+
+ /**
+ * <p>The identifyication of the <code>infinity</code> value
+ * in the <code>Depth</code> header.</p>
+ */
+ public static final int INFINITY = Integer.MAX_VALUE;
+
+ /** <p>The nested {@link HttpServletRequest}.</p> */
+ private HttpServletRequest request = null;
+ /** <p>The nested {@link HttpServletResponse}.</p> */
+ private HttpServletResponse response = null;
+ /** <p>The {@link URI} associated with the base of the repository.</p> */
+ private URI base = null;
+ /** <p>The status for the HTTP response.</p> */
+ private int status = -1;
+
+ /* ====================================================================== */
+ /* Constructors */
+ /* ====================================================================== */
+
+ /**
+ * <p>Create a new {@link DAVTransaction} instance.</p>
+ */
+ public DAVTransaction(ServletRequest request, ServletResponse response)
+ throws ServletException {
+ if (request == null) throw new NullPointerException("Null request");
+ if (response == null) throw new NullPointerException("Null response");
+ this.request = (HttpServletRequest) request;
+ this.response = (HttpServletResponse) response;
+ this.response.setHeader("DAV", "1");
+ this.response.setHeader("MS-Author-Via", "DAV");
+
+ try {
+ String scheme = this.request.getScheme();
+ String host = this.request.getServerName();
+ String path = this.request.getContextPath() +
+ this.request.getServletPath();
+ int port = this.request.getServerPort();
+ if (! path.endsWith("/")) path += "/";
+ this.base = new URI(scheme, null, host, port, path, null, null);
+ this.base = this.base.normalize();
+ } catch (URISyntaxException exception) {
+ throw new ServletException("Unable to create base URI", exception);
+ }
+ }
+
+ /* ====================================================================== */
+ /* Request methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Return the path originally requested by the client.</p>
+ */
+ public String getMethod() {
+ return this.request.getMethod();
+ }
+
+ /**
+ * <p>Return the path originally requested by the client.</p>
+ */
+ public String getOriginalPath() {
+ String path = this.request.getPathInfo();
+ if (path == null) return "";
+ if ((path.length() > 1) && (path.charAt(0) == '/')) {
+ return path.substring(1);
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * <p>Return the path originally requested by the client.</p>
+ */
+ public String getNormalizedPath() {
+ final String path = this.getOriginalPath();
+ if (! path.endsWith("/")) return path;
+ return path.substring(0, path.length() - 1);
+ }
+
+ /**
+ * <p>Return the depth requested by the client for this transaction.</p>
+ */
+ public int getDepth() {
+ String depth = request.getHeader("Depth");
+ if (depth == null) return INFINITY;
+ if ("infinity".equalsIgnoreCase(depth)) return INFINITY;
+ try {
+ return Integer.parseInt(depth);
+ } catch (NumberFormatException exception) {
+ throw new DAVException(412, "Unable to parse depth", exception);
+ }
+ }
+
+ /**
+ * <p>Return a {@link URI}
+ */
+ public URI getDestination() {
+ String destination = this.request.getHeader("Destination");
+ if (destination != null) try {
+ return this.base.relativize(new URI(destination));
+ } catch (URISyntaxException exception) {
+ throw new DAVException(412, "Can't parse destination", exception);
+ }
+ return null;
+ }
+
+ /**
+ * <p>Return the overwrite flag requested by the client for this
+ * transaction.</p>
+ */
+ public boolean getOverwrite() {
+ String overwrite = request.getHeader("Overwrite");
+ if (overwrite == null) return true;
+ if ("T".equals(overwrite)) return true;
+ if ("F".equals(overwrite)) return false;
+ throw new DAVException(412, "Unable to parse overwrite flag");
+ }
+
+ /**
+ * <p>Check if the client requested a date-based conditional operation.</p>
+ */
+ public Date getIfModifiedSince() {
+ String name = "If-Modified-Since";
+ if (this.request.getHeader(name) == null) return null;
+ return new Date(this.request.getDateHeader(name));
+ }
+
+ /* ====================================================================== */
+ /* Response methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Set the HTTP status code of the response.</p>
+ */
+ public void setStatus(int status) {
+ this.response.setStatus(status);
+ this.status = status;
+ }
+
+ /**
+ * <p>Set the HTTP status code of the response.</p>
+ */
+ public int getStatus() {
+ return this.status;
+ }
+
+ /**
+ * <p>Set the HTTP <code>Content-Type</code> header.</p>
+ */
+ public void setContentType(String type) {
+ this.response.setContentType(type);
+ }
+
+ /**
+ * <p>Set an HTTP header in the response.</p>
+ */
+ public void setHeader(String name, String value) {
+ this.response.setHeader(name, value);
+ }
+
+ /* ====================================================================== */
+ /* I/O methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Check if there is a body in the request.</p>
+ *
+ * <p>This method differs from checking if the return value of the
+ * {@link #read()} method is not <b>null</b> as a request body of length
+ * zero will return <b>false</b> in this case, while in the {@link #read()}
+ * method will return an empty {@link InputStream}.</p>
+ */
+ public boolean hasRequestBody()
+ throws IOException {
+ /* We don't support ranges */
+ if (request.getHeader("Content-Range") != null)
+ throw new DAVException(501, "Content-Range not supported");
+
+ if (this.request.getContentLength() > 0) return true;
+ String len = this.request.getHeader("Content-Length");
+ if (len != null) try {
+ return (Long.parseLong(len) > 0);
+ } catch (NumberFormatException exception) {
+ throw new DAVException(411, "Invalid Content-Length specified");
+ }
+ return false;
+ }
+
+ /**
+ * <p>Read from the body of the original request.</p>
+ */
+ public InputStream read()
+ throws IOException {
+ /* We don't support ranges */
+ if (request.getHeader("Content-Range") != null)
+ throw new DAVException(501, "Content-Range not supported");
+
+ if (this.request.getContentLength() >= 0) {
+ return this.request.getInputStream();
+ }
+
+ String len = this.request.getHeader("Content-Length");
+ if (len != null) try {
+ if (Long.parseLong(len) >= 0) return this.request.getInputStream();
+ } catch (NumberFormatException exception) {
+ throw new DAVException(411, "Invalid Content-Length specified");
+ }
+ return null;
+ }
+
+ /**
+ * <p>Write the body of the response.</p>
+ */
+ public OutputStream write()
+ throws IOException {
+ return this.response.getOutputStream();
+ }
+
+ /**
+ * <p>Write the body of the response.</p>
+ */
+ public PrintWriter write(String encoding)
+ throws IOException {
+ return new PrintWriter(new OutputStreamWriter(this.write(), encoding));
+ }
+
+ /* ====================================================================== */
+ /* Lookup methods */
+ /* ====================================================================== */
+
+ /**
+ * <p>Look up the final URI of a {@link DAVResource} as visible from the
+ * HTTP client requesting this transaction.</p>
+ */
+ public URI lookup(DAVResource resource) {
+ URI uri = resource.getRelativeURI();
+ return this.base.resolve(uri).normalize();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java
new file mode 100644
index 000000000..5e03f1e76
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java
@@ -0,0 +1,420 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+
+/**
+ * <p>A collection of static utilities.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public final class DAVUtilities {
+
+ /** <p>A {@link HashMap} of configured mime types.</p> */
+ private static Map MIME_TYPES = new HashMap();
+ /** <p>A {@link HashMap} of configured mime types.</p> */
+ private static Properties PROPERTIES = new Properties();
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_822 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
+ /** <p>The {@link SimpleDateFormat} RFC-822 date format.</p> */
+ private static final String FORMAT_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+ /** <p>The {@link TimeZone} to use for dates.</p> */
+ private static final TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
+ /** <p>The {@link Locale} to use for dates.</p> */
+ private static final Locale LOCALE = Locale.US;
+
+ /**
+ * <p>Load the mime types map from a resource.</p>
+ */
+ static {
+ Class clazz = DAVUtilities.class;
+ ClassLoader loader = clazz.getClassLoader();
+
+ /* Load up the properties file */
+ String webdavPropResource = "plexus-webdav/webdav.props";
+ InputStream prop = loader.getResourceAsStream(webdavPropResource);
+ if (prop != null) try {
+ DAVUtilities.PROPERTIES.load(prop);
+ prop.close();
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ } else {
+ System.err.println("Invalid resource: " + webdavPropResource);
+ }
+
+ /* Load up the mime types table */
+ String mimeTypeResource = "plexus-webdav/mime.types";
+ InputStream mime = loader.getResourceAsStream(mimeTypeResource);
+ if (mime != null) try {
+ InputStreamReader read = new InputStreamReader(mime);
+ BufferedReader buff = new BufferedReader(read);
+ String line = null;
+ while ((line = buff.readLine()) != null) {
+ line = line.trim();
+ if (line.length() == 0) continue;
+ if (line.charAt(0) == '#') continue;
+ StringTokenizer tokenizer = new StringTokenizer(line);
+ if (tokenizer.countTokens() > 1) {
+ String type = tokenizer.nextToken();
+ while (tokenizer.hasMoreTokens()) {
+ String extension = '.' + tokenizer.nextToken();
+ DAVUtilities.MIME_TYPES.put(extension, type);
+ }
+ }
+ }
+ buff.close();
+ read.close();
+ mime.close();
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ } else {
+ System.err.println("Invalid resource: " + mimeTypeResource);
+ }
+ }
+
+ /** <p>The signature of this package usable from a servlet.</p> */
+ public static final String SERVLET_SIGNATURE =
+ DAVUtilities.getProperty("servlet.signature") + '/' +
+ DAVUtilities.getProperty("version");
+
+ /** <p>The information detail of this package usable from a servlet.</p> */
+ public static final String SERVLET_INFORMATION =
+ DAVUtilities.getProperty("servlet.information") + " version " +
+ DAVUtilities.getProperty("version");
+
+ /**
+ * <p>Deny public construction of {@link DAVUtilities} instances.</p>
+ */
+ private DAVUtilities() {
+ super();
+ }
+
+ /**
+ * <p>Return the value of a property configured for this package.</p>
+ *
+ * @param name the property name
+ * @return a {@link String} instance or <b>null</b> if unknown.
+ */
+ public static String getProperty(String name) {
+ if (name == null) return null;
+ return DAVUtilities.PROPERTIES.getProperty(name);
+ }
+
+ /**
+ * <p>Return the MIME Type configured for a given resource.</p>
+ *
+ * @param name the resource name whose MIME Type needs to be looked up.
+ * @return a {@link String} instance or <b>null</b> if the type is unknown.
+ */
+ public static String getMimeType(String name) {
+ if (name == null) return null;
+
+ Iterator iterator = DAVUtilities.MIME_TYPES.keySet().iterator();
+ while (iterator.hasNext()) {
+ String extension = (String) iterator.next();
+ if (name.endsWith(extension)) {
+ return (String) DAVUtilities.MIME_TYPES.get(extension);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * <p>Return a {@link String} message given an HTTP status code.</p>
+ */
+ public static String getStatusMessage(int status) {
+ switch (status) {
+ /* HTTP/1.1 RFC-2616 */
+ case 100: return "100 Continue";
+ case 101: return "101 Switching Protocols";
+ case 200: return "200 OK";
+ case 201: return "201 Created";
+ case 202: return "202 Accepted";
+ case 203: return "203 Non-Authoritative Information";
+ case 204: return "204 No Content";
+ case 205: return "205 Reset Content";
+ case 206: return "206 Partial Content";
+ case 300: return "300 Multiple Choices";
+ case 301: return "301 Moved Permanently";
+ case 302: return "302 Found";
+ case 303: return "303 See Other";
+ case 304: return "304 Not Modified";
+ case 305: return "305 Use Proxy";
+ case 306: return "306 (Unused)";
+ case 307: return "307 Temporary Redirect";
+ case 400: return "400 Bad Request";
+ case 401: return "401 Unauthorized";
+ case 402: return "402 Payment Required";
+ case 403: return "403 Forbidden";
+ case 404: return "404 Not Found";
+ case 405: return "405 Method Not Allowed";
+ case 406: return "406 Not Acceptable";
+ case 407: return "407 Proxy Authentication Required";
+ case 408: return "408 Request Timeout";
+ case 409: return "409 Conflict";
+ case 410: return "410 Gone";
+ case 411: return "411 Length Required";
+ case 412: return "412 Precondition Failed";
+ case 413: return "413 Request Entity Too Large";
+ case 414: return "414 Request-URI Too Long";
+ case 415: return "415 Unsupported Media Type";
+ case 416: return "416 Requested Range Not Satisfiable";
+ case 417: return "417 Expectation Failed";
+ case 500: return "500 Internal Server Error";
+ case 501: return "501 Not Implemented";
+ case 502: return "502 Bad Gateway";
+ case 503: return "503 Service Unavailable";
+ case 504: return "504 Gateway Timeout";
+ case 505: return "505 HTTP Version Not Supported";
+
+ /* DAV/1.0 RFC-2518 */
+ case 102: return "102 Processing";
+ case 207: return "207 Multi-Status";
+ case 422: return "422 Unprocessable Entity";
+ case 423: return "423 Locked";
+ case 424: return "424 Failed Dependency";
+ case 507: return "507 Insufficient Storage";
+
+ /* Unknown */
+ default: return null;
+ }
+ }
+
+ /**
+ * <p>Format a {@link Number} into a {@link String} making sure that
+ * {@link NullPointerException}s are not thrown.</p>
+ *
+ * @param number the {@link Number} to format.
+ * @return a {@link String} instance or <b>null</b> if the object was null.
+ */
+ public static String formatNumber(Number number) {
+ if (number == null) return null;
+ return (number.toString());
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Long}.</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Long} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Long parseNumber(String string) {
+ if (string == null) return null;
+ try {
+ return new Long(string);
+ } catch (NumberFormatException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the HTTP/1.1 RFC.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatHttpDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Format a {@link Date} according to the ISO 8601 specification.</p>
+ *
+ * @param date the {@link Date} to format.
+ * @return a {@link String} instance or <b>null</b> if the date was null.
+ */
+ public static String formatIsoDate(Date date) {
+ if (date == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ return formatter.format(date);
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the
+ * HTTP/1.1 RFC (<code>Mon, 31 Jan 2000 11:59:00 GMT</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseHttpDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_822, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Parse a {@link String} into a {@link Date} according to the ISO 8601
+ * specification (<code>2000-12-31T11:59:00Z</code>).</p>
+ *
+ * @param string the {@link String} to parse.
+ * @return a {@link Date} instance or <b>null</b> if the date was null or
+ * if there was an error parsing the specified {@link String}.
+ */
+ public static Date parseIsoDate(String string) {
+ if (string == null) return null;
+ SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_ISO, LOCALE);
+ formatter.setTimeZone(TIMEZONE);
+ try {
+ return formatter.parse(string);
+ } catch (ParseException exception) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Return the HEX representation of an array of bytes.</p>
+ *
+ * @param buffer the array of bytes to convert in a HEX {@link String}.
+ * @return a <b>non-null</b> {@link String} instance.
+ */
+ public static String toHexString(byte buffer[]) {
+ char output[] = new char[buffer.length * 2];
+ int position = 0;
+ for (int x = 0; x < buffer.length; x++) {
+ output[position ++] = DAVUtilities.toHexDigit(buffer[x] >> 4);
+ output[position ++] = DAVUtilities.toHexDigit(buffer[x]);
+ }
+ return new String(output);
+ }
+
+ /**
+ * <p>Return the HEX representation of a long integer.</p>
+ *
+ * @param number the long to convert in a HEX {@link String}.
+ * @return a <b>non-null</b> 16-characters {@link String} instance.
+ */
+ public static String toHexString(long number) {
+ char output[] = new char[16];
+ output[0] = DAVUtilities.toHexDigit((int)(number >> 60));
+ output[1] = DAVUtilities.toHexDigit((int)(number >> 56));
+ output[2] = DAVUtilities.toHexDigit((int)(number >> 52));
+ output[3] = DAVUtilities.toHexDigit((int)(number >> 48));
+ output[4] = DAVUtilities.toHexDigit((int)(number >> 44));
+ output[5] = DAVUtilities.toHexDigit((int)(number >> 40));
+ output[6] = DAVUtilities.toHexDigit((int)(number >> 36));
+ output[7] = DAVUtilities.toHexDigit((int)(number >> 32));
+ output[8] = DAVUtilities.toHexDigit((int)(number >> 28));
+ output[9] = DAVUtilities.toHexDigit((int)(number >> 24));
+ output[10] = DAVUtilities.toHexDigit((int)(number >> 20));
+ output[11] = DAVUtilities.toHexDigit((int)(number >> 16));
+ output[12] = DAVUtilities.toHexDigit((int)(number >> 12));
+ output[13] = DAVUtilities.toHexDigit((int)(number >> 8));
+ output[14] = DAVUtilities.toHexDigit((int)(number >> 4));
+ output[15] = DAVUtilities.toHexDigit((int)(number));
+ return new String(output);
+ }
+
+ /**
+ * <p>Return the HEX representation of an integer.</p>
+ *
+ * @param number the int to convert in a HEX {@link String}.
+ * @return a <b>non-null</b> 8-characters {@link String} instance.
+ */
+ public static String toHexString(int number) {
+ char output[] = new char[8];
+ output[0] = DAVUtilities.toHexDigit((int)(number >> 28));
+ output[1] = DAVUtilities.toHexDigit((int)(number >> 24));
+ output[2] = DAVUtilities.toHexDigit((int)(number >> 20));
+ output[3] = DAVUtilities.toHexDigit((int)(number >> 16));
+ output[4] = DAVUtilities.toHexDigit((int)(number >> 12));
+ output[5] = DAVUtilities.toHexDigit((int)(number >> 8));
+ output[6] = DAVUtilities.toHexDigit((int)(number >> 4));
+ output[7] = DAVUtilities.toHexDigit((int)(number));
+ return new String(output);
+ }
+
+ /**
+ * <p>Return the HEX representation of a char.</p>
+ *
+ * @param number the char to convert in a HEX {@link String}.
+ * @return a <b>non-null</b> 4-characters {@link String} instance.
+ */
+ public static String toHexString(char number) {
+ char output[] = new char[4];
+ output[0] = DAVUtilities.toHexDigit((int)(number >> 12));
+ output[1] = DAVUtilities.toHexDigit((int)(number >> 8));
+ output[2] = DAVUtilities.toHexDigit((int)(number >> 4));
+ output[3] = DAVUtilities.toHexDigit((int)(number));
+ return new String(output);
+ }
+
+ /**
+ * <p>Return the HEX representation of a byte.</p>
+ *
+ * @param number the byte to convert in a HEX {@link String}.
+ * @return a <b>non-null</b> 2-characters {@link String} instance.
+ */
+ public static String toHexString(byte number) {
+ char output[] = new char[2];
+ output[0] = DAVUtilities.toHexDigit((int)(number >> 4));
+ output[1] = DAVUtilities.toHexDigit((int)(number));
+ return new String(output);
+ }
+
+ /**
+ * <p>Return the single digit character representing the HEX encoding of
+ * the lower four bits of a given integer.</p>
+ */
+ private static char toHexDigit(int number) {
+ switch (number & 0x0F) {
+ case 0x00: return '0';
+ case 0x01: return '1';
+ case 0x02: return '2';
+ case 0x03: return '3';
+ case 0x04: return '4';
+ case 0x05: return '5';
+ case 0x06: return '6';
+ case 0x07: return '7';
+ case 0x08: return '8';
+ case 0x09: return '9';
+ case 0x0A: return 'A';
+ case 0x0B: return 'B';
+ case 0x0C: return 'C';
+ case 0x0D: return 'D';
+ case 0x0E: return 'E';
+ case 0x0F: return 'F';
+ }
+ String message = "Invalid HEX digit " + Integer.toHexString(number);
+ throw new IllegalArgumentException(message);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java
new file mode 100644
index 000000000..57d34c6dc
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java
@@ -0,0 +1,113 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * <p>A {@link DAVRepository} instance enforcing all {@link DAVResource}s to
+ * be XML files.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class XMLRepository extends DAVRepository {
+
+ /**
+ * <p>Create a new {@link XMLRepository} instance.</p>
+ */
+ public XMLRepository(File root)
+ throws IOException {
+ super(root);
+ }
+
+ /**
+ * <p>Return the {@link DAVResource} associated with a {@link URI}.</p>
+ */
+ public DAVResource getResource(URI uri)
+ throws IOException {
+ return new XMLResource(this, super.getResource(uri));
+ }
+
+ /**
+ * <p>A simple {@link DAVResource} extension enforcing XML writes.</p>
+ */
+ private static final class XMLResource extends DAVResource {
+
+ /**
+ * <p>Create a new {@link XMLResource} instance.</p>
+ */
+ public XMLResource(XMLRepository repository, DAVResource resource) {
+ super(repository, resource.getFile());
+ }
+
+ /**
+ * <p>Override the MIME Content-Type to <code>text/xml</code> for
+ * normal resources.</p>
+ */
+ public String getContentType() {
+ if (this.isResource()) return "text/xml";
+ return super.getContentType();
+ }
+
+ /**
+ * <p>Return a {@link DAVOutputStream} enforcing XML formatted data.</p>
+ */
+ public DAVOutputStream write() {
+ return new XMLOutputStream(this);
+ }
+ }
+
+ /**
+ * <p>A simple {@link DAVOutputStream} enforcing XML formatted data.</p>
+ */
+ private static final class XMLOutputStream extends DAVOutputStream {
+
+ /**
+ * <p>Create a new {@link XMLOutputStream} instance.</p>
+ */
+ protected XMLOutputStream(XMLResource resource) {
+ super(resource);
+ }
+
+ /**
+ * <p>Ensure that whatever is in the temporary file is XML.</p>
+ */
+ protected void rename(File temporary, File original)
+ throws IOException {
+ try {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ SAXParser parser = factory.newSAXParser();
+ parser.parse(temporary, new DefaultHandler());
+ super.rename(temporary, original);
+ } catch (ParserConfigurationException exception) {
+ throw new DAVException(500, "JAXP parser error", exception);
+ } catch (SAXException exception) {
+ throw new DAVException(415, "Error parsing data", exception);
+ }
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java
new file mode 100644
index 000000000..c598bdc23
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java
@@ -0,0 +1,71 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVMultiStatus;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+import java.net.URI;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>COPY</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class COPY implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link COPY} instance.</p>
+ */
+ public COPY() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>COPY</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+
+ URI target = transaction.getDestination();
+ if (target == null) throw new DAVException(412, "No destination");
+ DAVResource dest = resource.getRepository().getResource(target);
+
+ int depth = transaction.getDepth();
+ boolean recursive = false;
+ if (depth == 0) {
+ recursive = false;
+ } else if (depth == DAVTransaction.INFINITY) {
+ recursive = true;
+ } else {
+ throw new DAVException(412, "Invalid Depth specified");
+ }
+
+ try {
+ resource.copy(dest, transaction.getOverwrite(), recursive);
+ transaction.setStatus(transaction.getOverwrite() ? 204 : 201);
+ } catch (DAVMultiStatus multistatus) {
+ multistatus.write(transaction);
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java
new file mode 100644
index 000000000..37192a8ee
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java
@@ -0,0 +1,54 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVMultiStatus;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>DELETE</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DELETE implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link DELETE} instance.</p>
+ */
+ public DELETE() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>DELETE</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ try {
+ resource.delete();
+ transaction.setStatus(204);
+ } catch (DAVMultiStatus multistatus) {
+ multistatus.write(transaction);
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java
new file mode 100644
index 000000000..e7dc3c66b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java
@@ -0,0 +1,144 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVInputStream;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a>
+ * <code>GET</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class GET extends HEAD {
+
+ /** <p>The encoding charset to repsesent collections.</p> */
+ public static final String ENCODING = "UTF-8";
+
+ /** <p>The mime type that {@link GET} will use serving collections.</p> */
+ public static final String COLLECTION_MIME_TYPE = "text/html ;charset=\""
+ + ENCODING + "\"";
+
+ /**
+ * <p>Create a new {@link GET} instance.</p>
+ */
+ public GET() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>GET</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ super.process(transaction, resource);
+
+ final String originalPath = transaction.getOriginalPath();
+ final String normalizedPath = transaction.getNormalizedPath();
+ final String current;
+ final String parent;
+ if (originalPath.equals(normalizedPath)) {
+ final String relativePath = resource.getRelativePath();
+ if (relativePath.equals("")) {
+ current = transaction.lookup(resource).toASCIIString();
+ } else {
+ current = relativePath;
+ }
+ parent = "./";
+ } else {
+ current = "./";
+ parent = "../";
+ }
+
+ if (resource.isCollection()) {
+ transaction.setHeader( "Content-Disposition", "inline; filename=\"index.html\"");
+ PrintWriter out = transaction.write(ENCODING);
+ String path = resource.getRelativePath();
+ out.println("<html>");
+ out.println("<head>");
+ out.println("<title>Collection: /" + path + "</title>");
+ out.println("</head>");
+ out.println("<body>");
+ out.println("<h2>Collection: /" + path + "</h2>");
+ out.println("<ul>");
+
+ /* Process the parent */
+ final DAVResource parentResource = resource.getParent();
+ if (parentResource != null) {
+ out.print("<li><a href=\"");
+ out.print(parent);
+ out.print("\">");
+ out.print(parentResource.getDisplayName());
+ out.println("</a> <i><small>(Parent)</small></i></li>");
+ out.println("</ul>");
+ out.println("<ul>");
+ }
+
+ /* Process the children (in two sorted sets, for nice ordering) */
+ Set resources = new TreeSet();
+ Set collections = new TreeSet();
+ Iterator iterator = resource.getChildren();
+ while (iterator.hasNext()) {
+ final DAVResource child = (DAVResource) iterator.next();
+ final StringBuffer buffer = new StringBuffer();
+ final String childPath = child.getDisplayName();
+ buffer.append("<li><a href=\"");
+ buffer.append(current);
+ buffer.append(childPath);
+ buffer.append("\">");
+ buffer.append(childPath);
+ buffer.append("</li>");
+ if (child.isCollection()) {
+ collections.add(buffer.toString());
+ } else {
+ resources.add(buffer.toString());
+ }
+ }
+
+ /* Spit out the collections first and the resources then */
+ for (Iterator i = collections.iterator(); i.hasNext(); )
+ out.println(i.next());
+ for (Iterator i = resources.iterator(); i.hasNext(); )
+ out.println(i.next());
+
+ out.println("</ul>");
+ out.println("</body>");
+ out.println("</html>");
+ out.flush();
+ return;
+ }
+
+ /* Processing a normal resource request */
+ OutputStream out = transaction.write();
+ DAVInputStream in = resource.read();
+ byte buffer[] = new byte[4096];
+ int k = -1;
+ while ((k = in.read(buffer)) != -1) out.write(buffer, 0, k);
+ in.close();
+ out.flush();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java
new file mode 100644
index 000000000..029437f12
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java
@@ -0,0 +1,79 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVNotModified;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+import it.could.webdav.DAVUtilities;
+
+import java.io.IOException;
+import java.util.Date;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a>
+ * <code>HEAD</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class HEAD implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link HEAD} instance.</p>
+ */
+ public HEAD() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>HEAD</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ /* Check if we have to force a resource not found or a redirection */
+ if (resource.isNull())
+ throw new DAVException(404, "Not found", resource);
+
+ /* Check if this is a conditional (processable only for resources) */
+ Date ifmod = transaction.getIfModifiedSince();
+ Date lsmod = resource.getLastModified();
+ if (resource.isResource() && (ifmod != null) && (lsmod != null)) {
+ /* HTTP doesn't send milliseconds, but Java does, so, reset them */
+ lsmod = new Date(((long)(lsmod.getTime() / 1000)) * 1000);
+ if (!ifmod.before(lsmod)) throw new DAVNotModified(resource);
+ }
+
+ /* Get the headers of this method */
+ String ctyp = resource.getContentType();
+ String etag = resource.getEntityTag();
+ String lmod = DAVUtilities.formatHttpDate(resource.getLastModified());
+ String clen = DAVUtilities.formatNumber(resource.getContentLength());
+
+ /* Set the normal headers that are required for a GET */
+ if (resource.isCollection()) {
+ transaction.setContentType(GET.COLLECTION_MIME_TYPE);
+ } else if (ctyp != null) {
+ transaction.setContentType(ctyp);
+ }
+ if (etag != null) transaction.setHeader("ETag", etag);
+ if (lmod != null) transaction.setHeader("Last-Modified", lmod);
+ if (clen != null) transaction.setHeader("Content-Length", clen);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java
new file mode 100644
index 000000000..167d4a167
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java
@@ -0,0 +1,57 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>MKCOL</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class MKCOL implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link MKCOL} instance.</p>
+ */
+ public MKCOL() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>MKCOL</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+
+ /* Unsupported media type, we don't want content */
+ if (transaction.hasRequestBody()) {
+ throw new DAVException (415, "No request body allowed in request");
+ }
+
+ /* Create the collection */
+ resource.makeCollection();
+ transaction.setStatus(201);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java
new file mode 100644
index 000000000..672636beb
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java
@@ -0,0 +1,80 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVMultiStatus;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+import java.net.URI;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>MOVE</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class MOVE implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link MOVE} instance.</p>
+ */
+ public MOVE() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>MOVE</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ URI target = transaction.getDestination();
+ if (target == null) throw new DAVException(412, "No destination");
+ DAVResource dest = resource.getRepository().getResource(target);
+
+ int depth = transaction.getDepth();
+ boolean recursive = false;
+ if (depth == 0) {
+ recursive = false;
+ } else if (depth == DAVTransaction.INFINITY) {
+ recursive = true;
+ } else {
+ throw new DAVException(412, "Invalid Depth specified");
+ }
+
+ try {
+ int status;
+ if(! dest.isNull() && ! transaction.getOverwrite()) {
+ status = 412; // MOVE-on-existing should fail with 412
+ } else {
+ resource.move(dest, transaction.getOverwrite(), recursive);
+ if(transaction.getOverwrite()) {
+ status = 204; // No Content
+ } else {
+ status = 201; // Created
+ }
+ }
+ transaction.setStatus(status);
+ } catch (DAVMultiStatus multistatus) {
+ multistatus.write(transaction);
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java
new file mode 100644
index 000000000..0228c09dc
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java
@@ -0,0 +1,52 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVProcessor;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a>
+ * <code>OPTIONS</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class OPTIONS implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link OPTIONS} instance.</p>
+ */
+ public OPTIONS() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>OPTIONS</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ transaction.setHeader("Content-Type", resource.getContentType());
+ transaction.setHeader("Allow", DAVProcessor.METHODS);
+ transaction.setStatus(200);
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java
new file mode 100644
index 000000000..3854f20f0
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java
@@ -0,0 +1,122 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+import it.could.webdav.DAVUtilities;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Iterator;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>PROPFIND</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class PROPFIND implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link PROPFIND} instance.</p>
+ */
+ public PROPFIND() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>PROPFIND</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ /* Check if we have to force a resource not found or a redirection */
+ if (resource.isNull())
+ throw new DAVException(404, "Not found", resource);
+
+ /* Check depth */
+ int depth = transaction.getDepth();
+ if (depth > 1) new DAVException(403, "Invalid depth");
+
+ /* What to do on a collection resource */
+ transaction.setStatus(207);
+ transaction.setContentType("text/xml; charset=\"UTF-8\"");
+ PrintWriter out = transaction.write("UTF-8");
+
+ /* Output the XML declaration and the root document tag */
+ out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ out.println("<D:multistatus xmlns:D=\"DAV:\">");
+
+ /* Process this resource's property (always) */
+ this.process(transaction, out, resource);
+
+ /* Process this resource's children (if required) */
+ if (resource.isCollection() && (depth > 0)) {
+ Iterator children = resource.getChildren();
+ while (children.hasNext()) {
+ DAVResource child = (DAVResource) children.next();
+ this.process(transaction, out, child);
+ }
+ }
+
+ /* Close up the XML Multi-Status response */
+ out.println("</D:multistatus>");
+ out.flush();
+ }
+
+ private void process(DAVTransaction txn, PrintWriter out, DAVResource res) {
+ /* The href of the resource is only the absolute path */
+ out.println(" <D:response>");
+ out.println(" <D:href>" + txn.lookup(res).getPath() + "</D:href>");
+ out.println(" <D:propstat>");
+ out.println(" <D:prop>");
+
+ /* Figure out what we're dealing with here */
+ if (res.isCollection()) {
+ this.process(out, "resourcetype", "<D:collection/>");
+ }
+ this.process(out, "getcontenttype", res.getContentType());
+
+ this.process(out, "getetag", res.getEntityTag());
+ String date = DAVUtilities.formatIsoDate(res.getCreationDate());
+ this.process(out, "creationdate", date);
+ String lmod = DAVUtilities.formatHttpDate(res.getLastModified());
+ this.process(out, "getlastmodified", lmod);
+ String clen = DAVUtilities.formatNumber(res.getContentLength());
+ this.process(out, "getcontentlength", clen);
+
+ out.println(" </D:prop>");
+ out.println(" <D:status>HTTP/1.1 200 OK</D:status>");
+ out.println(" </D:propstat>");
+ out.println(" </D:response>");
+ }
+
+ private void process(PrintWriter out, String name, String value) {
+ if (value == null) return;
+ out.print(" <D:");
+ out.print(name);
+ out.print(">");
+ out.print(value);
+ out.print("</D:");
+ out.print(name);
+ out.println(">");
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java
new file mode 100644
index 000000000..fbddd0e84
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java
@@ -0,0 +1,55 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>PROPPATCH</code> metohd implementation.</p>
+ *
+ * <p>As this servlet does not handle the creation of custom properties, this
+ * method will always fail with a <code>403</code> (Forbidden).</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class PROPPATCH implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link PROPPATCH} instance.</p>
+ */
+ public PROPPATCH() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>PROPPATCH</code> method.</p>
+ *
+ * <p>As this servlet does not handle the creation of custom properties,
+ * this method will always fail with a <code>403</code> (Forbidden).</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ throw new DAVException(403, "All properties are immutable");
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java
new file mode 100644
index 000000000..fb995e69d
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java
@@ -0,0 +1,72 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.methods;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVOutputStream;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * <p><a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ * <code>PUT</code> metohd implementation.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class PUT implements DAVMethod {
+
+ /**
+ * <p>Create a new {@link PUT} instance.</p>
+ */
+ public PUT() {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>PUT</code> method.</p>
+ */
+ public void process(DAVTransaction transaction, DAVResource resource)
+ throws IOException {
+ /*
+ * The HTTP status code will depend on the existance of the resource:
+ * if not found: HTTP/1.1 201 Created
+ * if existing: HTTP/1.1 204 No Content
+ */
+ transaction.setStatus(resource.isNull()? 201: 204);
+
+ /* Open the streams for reading and writing */
+ InputStream in = transaction.read();
+ if (in == null) throw new DAVException(411, "Content-Length required");
+ DAVOutputStream out = resource.write();
+
+ /* Write the content from the PUT to the specified resource */
+ try {
+ byte buffer[] = new byte[4096];
+ int k = -1;
+ while ((k = in.read(buffer)) != -1) out.write(buffer, 0, k);
+ in.close();
+ out.close();
+ } finally {
+ out.abort();
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html
new file mode 100644
index 000000000..f74f01889
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>Could.IT WebDAV Servlet</title>
+ </head>
+ <body>
+ <p>
+ This package contains the implementation of all Level 1
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ methods provided by this framework.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html
new file mode 100644
index 000000000..1354f9f33
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html
@@ -0,0 +1,134 @@
+<html>
+ <head>
+ <title>Could.IT WebDAV Servlet</title>
+ </head>
+ <body>
+ <p>
+ This package contains a minimal
+ <a href="http://java.sun.com/products/servlet/">Servlet</a>
+ based implementation of the
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ specification.
+ </p>
+ <p>
+ This implementation does not in any way try to replace or extend the
+ <a href="http://jakarta.apache.org/slide/">Apache Slide</a>
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ implementation, but tries to provide a <i>very light</i> and <i>extremely
+ minimal</i> alternative to be used in those scenarios where space is
+ a constraint (the <code>.jar</code> file is less than 100 kylobites),
+ and advanced features are not required.
+ </p>
+ <p>
+ The most visible limitations of this approach is that this
+ implementation does not offer any support for the <code>LOCK</code>
+ method (it is therefore not <i>DAV Level 2</i> compliant), and that
+ there limited support for properties:
+ </p>
+ <ul>
+ <li>
+ The <code>PROPFIND</code> will only return the <i>read-only</i>
+ <code>getcontenttype</code>, <code>getlastmodified</code>,
+ <code>getcontentlength</code>, <code>getetag</code> and
+ <code>resourcetype</code> properties.
+ </li>
+ <li>
+ The <code>PROPPATCH</code> will <i>always</i> fail with a
+ <code>403</code> <i>Not Found</i> error.
+ </li>
+ </ul>
+ <p>
+ Another important limitation is that this implementation will only and
+ exclusively provide access to a {@link java.io.File} based backend.
+ If you want to deploy your repository on another kind of backend (such
+ as SQL databases) please look at the WebDAV implementation provided by
+ <a href="http://jakarta.apache.org/slide/">Apache Slide</a>.
+ </p>
+
+ <h2>Configuration</h2>
+ <p>
+ The main entry point of this implementation is defined in the
+ {@link it.could.webdav.DAVServlet} class, which will handle all
+ <a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a> and
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a> requests
+ for the URI path it is configured to handle.
+ </p>
+ <p>
+ To operate properly the {@link it.could.webdav.DAVServlet} class
+ must be configured in the web-application's <code>web.xml</code>
+ deployment descriptor. The relevant parts of a snippet of an example
+ configuration deployment descriptor might look like the following:
+ </p>
+ <pre>
+&lt;servlet&gt;
+ &lt;servlet-name&gt;dav&lt;/servlet-name&gt;
+ &lt;servlet-class&gt;it.could.webdav.DAVServlet&lt;/servlet-class&gt;
+ &lt;init-param&gt;
+ &lt;param-name&gt;rootPath&lt;/param-name&gt;
+ &lt;param-value&gt;dav&lt;/param-value&gt;
+ &lt;/init-param&gt;
+ &lt;init-param&gt;
+ &lt;param-name&gt;xmlOnly&lt;/param-name&gt;
+ &lt;param-value&gt;false&lt;/param-value&gt;
+ &lt;/init-param&gt;
+ &lt;init-param&gt;
+ &lt;param-name&gt;debugEnabled&lt;/param-name&gt;
+ &lt;param-value&gt;false&lt;/param-value&gt;
+ &lt;/init-param&gt;
+ &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
+&lt;/servlet&gt;
+
+...
+
+&lt;servlet-mapping&gt;
+ &lt;servlet-name&gt;dav&lt;/servlet-name&gt;
+ &lt;url-pattern&gt;/dav/*&lt;/url-pattern&gt;
+&lt;/servlet-mapping&gt;
+ </pre>
+ <p>
+ In this example the {@link it.could.webdav.DAVServlet} servlet
+ is configured with all parameters its parameters:
+ </p>
+ <dl>
+ <dt>rootPath</dt>
+ <dd>
+ <i>[required]</i> This parameter indicates the path of the root of the
+ repository.<br />
+ If the specified parameter represents a relative path, it will be
+ treated as a {@link javax.servlet.ServletContext#getResource(String)
+ ServletContext resource}.<br />
+ Note that if you choose to distribute your web application in a
+ <code>.war</code> archive, your container will have to expand it
+ before intializing the {@link javax.servlet.ServletContext} as this
+ this implementation <i>requires</i> a {@link java.io.File} based
+ repository.
+ </dd>
+ <dt>xmlOnly</dt>
+ <dd>
+ <i>[optional, default=&quot;<code>false</code>&quot;]</i> This parameter
+ will instruct the {@link it.could.webdav.DAVServlet} to create
+ a very specialized version of the repository accepting only
+ <a href="http://www.w3.org/TR/REC-xml/#sec-well-formed">well-formed
+ XML</a> resources and collections.<br />
+ Note that when set to <code>true</code> this implementation will rely
+ on the <a href="http://java.sun.com/xml/jaxp/">JAXP</a> specification
+ to access a XML parser used to verify the <code>PUT</code> content.
+ </dd>
+ <dt>debugEnabled</dt>
+ <dd>
+ <i>[optional, default=&quot;<code>false</code>&quot;]</i> This parameter
+ will instruct the {@link it.could.webdav.DAVServlet} to log
+ unimportant debugging information (such as the methods called by the
+ client) in the {@link javax.servlet.ServletContext#log(String) context
+ log}.
+ </dd>
+ </dl>
+ <p>
+ The configured {@link it.could.webdav.DAVServlet} will then have
+ to be mapped to a path, and in the example above, every request for
+ any URL beginning in <code>/dav/</code> will be handled by this
+ implementation, with a repository rooted in the <code>/dav/</code>
+ directory of the web application.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java
new file mode 100644
index 000000000..56fc2e499
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java
@@ -0,0 +1,242 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.replication;
+
+import it.could.util.StreamTools;
+import it.could.util.http.WebDavClient;
+import it.could.util.location.Location;
+import it.could.webdav.DAVListener;
+import it.could.webdav.DAVLogger;
+import it.could.webdav.DAVRepository;
+import it.could.webdav.DAVResource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>TODO: Document this class.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVReplica extends Thread implements DAVListener {
+
+ private static final int SYNCHRONIZE = -1;
+
+ private final DAVRepository repository;
+ private final DAVLogger logger;
+ private final Location location;
+ private final List actions = new ArrayList();
+
+ public DAVReplica(DAVRepository repository, Location location,
+ DAVLogger logger)
+ throws IOException {
+ this.location = new WebDavClient(location).getLocation();
+ this.repository = repository;
+ this.logger = logger;
+ this.start();
+ }
+
+ public void synchronize()
+ throws IOException {
+ this.logger.log("Scheduling full synchronization");
+ this.notify(this.repository.getResource((String)null), SYNCHRONIZE);
+ }
+
+ public void notify(DAVResource resource, int event) {
+ this.logger.debug("Event for \"" + resource.getRelativePath() + "\"");
+ if (resource.getRepository() != this.repository) return;
+ synchronized (this.actions) {
+ this.actions.add(new Action(resource, event));
+ this.actions.notify();
+ }
+ }
+
+ public void run() {
+ this.logger.debug("Starting background replica thread on " + location);
+ while (true) try {
+ final DAVReplica.Action array[];
+ synchronized(this.actions) {
+ try {
+ if (this.actions.isEmpty()) this.actions.wait();
+ final int s = this.actions.size();
+ array = (Action []) this.actions.toArray(new Action[s]);
+ this.actions.clear();
+ } catch (InterruptedException exception) {
+ this.logger.debug("Exiting background replica thread");
+ return;
+ }
+ }
+
+ for (int x = 0; x < array.length; x ++) try {
+ this.replicate(array[x]);
+ } catch (Throwable throwable) {
+ final String path = array[x].resource.getRelativePath();
+ final String message = "Error synchronizing resource " + path;
+ this.logger.log(message, throwable);
+ }
+ } catch (Throwable throwable) {
+ this.logger.log("Replica thread attempted suicide", throwable);
+ }
+ }
+
+ private void replicate(DAVReplica.Action action) {
+ final DAVResource resource = action.resource;
+
+ if (action.event == SYNCHRONIZE) {
+ this.synchronize(resource);
+
+ } else try {
+ final String path = resource.getParent().getRelativePath();
+ final Location location = this.location.resolve(path);
+ final WebDavClient client = new WebDavClient(location);
+ final String child = resource.getName();
+
+ switch(action.event) {
+ case RESOURCE_CREATED:
+ case RESOURCE_MODIFIED:
+ this.logger.debug("Putting resource " + path);
+ this.put(resource, client);
+ break;
+ case RESOURCE_REMOVED:
+ case COLLECTION_REMOVED:
+ this.logger.debug("Deleting resource " + path);
+ client.delete(child);
+ break;
+ case COLLECTION_CREATED:
+ this.logger.debug("Creating collection " + path);
+ client.mkcol(child);
+ break;
+ }
+ } catch (IOException exception) {
+ String message = "Error replicating " + resource.getRelativePath();
+ this.logger.log(message, exception);
+ }
+ }
+
+ private void put(DAVResource resource, WebDavClient client)
+ throws IOException {
+ final String name = resource.getName();
+ final long length = resource.getContentLength().longValue();
+ final OutputStream output = client.put(name, length);
+ final InputStream input = resource.read();
+ StreamTools.copy(input, output);
+ }
+
+ private void synchronize(DAVResource resource) {
+ /* Figure out the path of the resource */
+ final String path = resource.getRelativePath();
+
+ /* If it's a file or null, just skip the whole thing */
+ if (! resource.isCollection()) {
+ this.logger.log("Synchronization on non-collection " + path);
+ return;
+ }
+
+ /* Open a webdav client to the collection to synchronize */
+ this.logger.log("Synchronizing collection " + path);
+ final WebDavClient client;
+ try {
+ final Location location = this.location.resolve(path);
+ client = new WebDavClient(location);
+ } catch (IOException exception) {
+ this.logger.log("Error creating WebDAV client", exception);
+ return;
+ }
+
+ /* Create a list of all children from the DAV client */
+ final Set children = new HashSet();
+ for (Iterator iter = client.iterator(); iter.hasNext(); )
+ children.add(iter.next());
+
+ /* Process all resource children one by one and ensure they exist */
+ for (Iterator iter = resource.getChildren(); iter.hasNext(); ) {
+ final DAVResource child = (DAVResource) iter.next();
+ final String name = child.getName();
+
+ /* Remove this from the resources that will be removed later */
+ children.remove(name);
+
+ /* If the client doesn't have this child, add it to the replica */
+ if (! client.hasChild(name)) try {
+ if (child.isCollection()) {
+ this.logger.debug("Client doesn't have collection " + name);
+ client.mkcol(name);
+ this.synchronize(child);
+
+ } else {
+ this.logger.debug("Client doesn't have resource " + name);
+ this.put(child, client);
+ }
+ } catch (IOException exception) {
+ this.logger.log("Error creating new child " + name, exception);
+
+ /* If this child is a collection, it must be a collection on dav */
+ } else if (child.isCollection()) try {
+ if (!client.isCollection(name)) {
+ this.logger.debug("Recreating collection " + name);
+ client.delete(name).mkcol(name);
+ }
+ this.synchronize(child);
+ } catch (IOException exception) {
+ this.logger.log("Error creating collection " + name, exception);
+
+ /* Ok, the resource is a normal one, verify size and timestamp */
+ } else try {
+ final Date rlast = child.getLastModified();
+ final Date dlast = client.getLastModified(name);
+ if ((rlast != null) && (rlast.equals(dlast))) {
+ final Long rlen = child.getContentLength();
+ final long dlen = client.getContentLength(name);
+ if ((rlen == null) || (rlen.longValue() != dlen)) {
+ this.logger.debug("Resending resource " + name);
+ this.put(child, client.delete(name));
+ }
+ }
+ } catch (IOException exception) {
+ this.logger.log("Error resending resource " + name, exception);
+ }
+ }
+
+ /* Any other child that was not removed above, will go away now! */
+ for (Iterator iter = children.iterator(); iter.hasNext(); ) {
+ final String name = (String) iter.next();
+ try {
+ this.logger.debug("Removing leftovers " + name);
+ client.delete(name);
+ } catch (IOException exception) {
+ this.logger.log("Error removing left over " + name, exception);
+ }
+ }
+ }
+
+ private static final class Action {
+ final DAVResource resource;
+ final int event;
+
+ private Action(DAVResource resource, int event) {
+ this.resource = resource;
+ this.event = event;
+ }
+ }
+} \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java
new file mode 100644
index 000000000..c7de67b14
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java
@@ -0,0 +1,131 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package it.could.webdav.replication;
+
+import it.could.util.location.Location;
+import it.could.webdav.DAVListener;
+import it.could.webdav.DAVLogger;
+import it.could.webdav.DAVRepository;
+import it.could.webdav.DAVServlet;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * <p>The {@link DAVReplicator} class is a {@link DAVListener} replicating
+ * all content to the WebDAV repository specified at construction.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ */
+public class DAVReplicator extends HttpServlet {
+
+ /** <p>The {@link DAVReplica} instances managed by this.</p> */
+ private final List replicas = new ArrayList();
+
+ /**
+ * <p>Create a new {@link DAVServlet} instance.</p>
+ */
+ public DAVReplicator() {
+ super();
+ }
+
+ /**
+ * <p>Initialize this {@link Servlet} instance.</p>
+ *
+ * <p>This servlet requires a couple of initialization parameters: the
+ * first one is &quot;<code>repository</code>&quot; indicating the name of
+ * the {@link DAVServlet} in the &quot;<code>web.xml</code>&quot; deployment
+ * descriptor whose repository should be replicated.</p>
+ *
+ * <p>The second required parameter &quot;<code>replicas</code>&quot;
+ * must contain a (whitespace separated list of) URL(s) where the original
+ * repository should be replicated to.</p>
+ *
+ * <p>Finally, when set to <code>true</code>, the optional parameter
+ * <code>debugEnabled</code> will enable logging of method invocation and
+ * events in the repository.</p>
+ */
+ public void init(ServletConfig config)
+ throws ServletException {
+ /* Initialize the super, just in case, and remember the context */
+ super.init(config);
+
+ /* Setup logging */
+ boolean debug = "true".equals(config.getInitParameter("debugEnabled"));
+ DAVLogger logger = new DAVLogger(config, debug);
+
+ /* Try to retrieve the WebDAV repository from the servlet context */
+ final String repositoryName = config.getInitParameter("repository");
+ final DAVRepository repository;
+ if (repositoryName == null) {
+ throw new ServletException("Parameter \"rootPath\" not specified");
+ } else try {
+ final String key = DAVServlet.getRepositoryKey(repositoryName);
+ final ServletContext context = config.getServletContext();
+ repository = (DAVRepository) context.getAttribute(key);
+ if (repository == null)
+ throw new ServletException("Unable to access repository from " +
+ "servlet \"" + repository + "\"");
+ } catch (ClassCastException exception) {
+ final String message = "Class cast exception accessing repository";
+ throw new ServletException(message, exception);
+ }
+
+ /* Access the different WebDAV replicas */
+ final String replicas = config.getInitParameter("replicas");
+ if (replicas == null) {
+ throw new ServletException("Parameter \"replicas\" not specified");
+ }
+
+ try {
+ final StringTokenizer tokenizer = new StringTokenizer(replicas);
+ while (tokenizer.hasMoreTokens()) {
+ final Location location = Location.parse(tokenizer.nextToken());
+ final DAVReplica replica = new DAVReplica(repository, location,
+ logger);
+ logger.log("Added repository replica to \"" + location + "\"");
+ repository.addListener(replica);
+ this.replicas.add(replica);
+ replica.synchronize();
+ }
+ } catch (IOException exception) {
+ throw new ServletException("Error creating replica", exception);
+ }
+
+ /* Check that we have at least one replica in */
+ if (this.replicas.size() != 0) return;
+ throw new ServletException("No replicas specified for repository");
+ }
+
+ /**
+ * <p>Destroy {@link DAVServlet} instance interrupting all running
+ * {@link DAVReplica} instances.</p>
+ */
+ public void destroy() {
+ for (Iterator iter = this.replicas.iterator(); iter.hasNext() ; ) {
+ ((DAVReplica) iter.next()).interrupt();
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html
new file mode 100644
index 000000000..4ca6e5c1c
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>Could.IT WebDAV Servlet</title>
+ </head>
+ <body>
+ <p>
+ This package contains a framework for maintaining fully replicated
+ <a href="http://www.rfc-editor.org/rfc/rfc2518.txt">WebDAV</a>
+ repositories.
+ </p>
+ </body>
+</html> \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java
new file mode 100644
index 000000000..c674f6b9e
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * AbstractDavServerComponent
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractDavServerComponent.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public abstract class AbstractDavServerComponent
+ implements DavServerComponent
+{
+ private List listeners;
+ protected boolean useIndexHtml = false;
+
+ public AbstractDavServerComponent()
+ {
+ listeners = new ArrayList();
+ }
+
+ public void addListener( DavServerListener listener )
+ {
+ listeners.add( listener );
+ }
+
+ public void removeListener( DavServerListener listener )
+ {
+ listeners.remove( listener );
+ }
+
+ protected void triggerCollectionCreated( String resource )
+ {
+ Iterator it = listeners.iterator();
+ while ( it.hasNext() )
+ {
+ DavServerListener listener = (DavServerListener) it.next();
+ try
+ {
+ listener.serverCollectionCreated( this, resource );
+ }
+ catch ( Exception e )
+ {
+ /* ignore error */
+ }
+ }
+ }
+
+ protected void triggerCollectionRemoved( String resource )
+ {
+ Iterator it = listeners.iterator();
+ while ( it.hasNext() )
+ {
+ DavServerListener listener = (DavServerListener) it.next();
+ try
+ {
+ listener.serverCollectionRemoved( this, resource );
+ }
+ catch ( Exception e )
+ {
+ /* ignore error */
+ }
+ }
+ }
+
+ protected void triggerResourceCreated( String resource )
+ {
+ Iterator it = listeners.iterator();
+ while ( it.hasNext() )
+ {
+ DavServerListener listener = (DavServerListener) it.next();
+ try
+ {
+ listener.serverResourceCreated( this, resource );
+ }
+ catch ( Exception e )
+ {
+ /* ignore error */
+ }
+ }
+ }
+
+ protected void triggerResourceRemoved( String resource )
+ {
+ Iterator it = listeners.iterator();
+ while ( it.hasNext() )
+ {
+ DavServerListener listener = (DavServerListener) it.next();
+ try
+ {
+ listener.serverResourceRemoved( this, resource );
+ }
+ catch ( Exception e )
+ {
+ /* ignore error */
+ }
+ }
+ }
+
+ protected void triggerResourceModified( String resource )
+ {
+ Iterator it = listeners.iterator();
+ while ( it.hasNext() )
+ {
+ DavServerListener listener = (DavServerListener) it.next();
+ try
+ {
+ listener.serverResourceModified( this, resource );
+ }
+ catch ( Exception e )
+ {
+ /* ignore error */
+ }
+ }
+ }
+
+ public boolean hasResource( String resource )
+ {
+ File rootDir = getRootDirectory();
+ if ( rootDir == null )
+ {
+ return false;
+ }
+ File resourceFile = new File( rootDir, resource );
+ return resourceFile.exists();
+ }
+
+ public boolean isUseIndexHtml()
+ {
+ return this.useIndexHtml;
+ }
+
+ public void setUseIndexHtml( boolean useIndexHtml )
+ {
+ this.useIndexHtml = useIndexHtml;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java
new file mode 100644
index 000000000..db4389f46
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * DavServerComponent
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DavServerComponent.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public interface DavServerComponent
+{
+ /** The Plexus ROLE name */
+ public static final String ROLE = DavServerComponent.class.getName();
+
+ /**
+ * Get the Prefix for this server component.
+ * @return the prefix associated with this component.
+ */
+ public String getPrefix();
+
+ /**
+ * Set the prefix for this server component.
+ * @param prefix the prefix to use.
+ */
+ public void setPrefix( String prefix );
+
+ /**
+ * <p>
+ * Flag to indicate how the dav server component should treat a GET request against
+ * a DAV Collection.
+ * </p>
+ *
+ * <p>
+ * If true, the collection being requested will be searched for an index.html (or index.htm)
+ * file to serve back, before it defaults to displaying the collection (directory) contents.
+ * </p>
+ *
+ * <p>
+ * If false, the collection will always be presented in as a list of contents.
+ * </p>
+ *
+ * @return true to use the index.html instead of directory contents.
+ */
+ public boolean isUseIndexHtml();
+
+ /**
+ * <p>
+ * Flag to indicate how the dav server component should treat a GET request against
+ * a DAV Collection.
+ * </p>
+ *
+ * <p>
+ * If true, the collection being requested will be searched for an index.html (or index.htm)
+ * file to serve back, before it defaults to displaying the collection (directory) contents.
+ * </p>
+ *
+ * <p>
+ * If false, the collection will always be presented in as a list of contents.
+ * </p>
+ *
+ * @param useIndexHtml true to use the index.html instead of directory contents.
+ */
+ public void setUseIndexHtml( boolean useIndexHtml );
+
+ /**
+ * Get the root directory for this server.
+ *
+ * @return the root directory for this server.
+ */
+ public File getRootDirectory();
+
+ /**
+ * Set the root directory for this server's content.
+ *
+ * @param rootDirectory the root directory for this server's content.
+ */
+ public void setRootDirectory( File rootDirectory );
+
+ /**
+ * Add a Server Listener for this server component.
+ *
+ * @param listener the listener to add for this component.
+ */
+ public void addListener( DavServerListener listener );
+
+ /**
+ * Remove a server listener for this server component.
+ *
+ * @param listener the listener to remove.
+ */
+ public void removeListener( DavServerListener listener );
+
+ /**
+ * Perform any initialization needed.
+ *
+ * @param servletConfig the servlet config that might be needed.
+ * @throws DavServerException if there was a problem initializing the server component.
+ */
+ public void init( ServletConfig servletConfig ) throws DavServerException;
+
+ /**
+ * Performs a simple filesystem check for the specified resource.
+ *
+ * @param resource the resource to check for.
+ * @return true if the resource exists.
+ */
+ public boolean hasResource( String resource );
+
+ /**
+ * Process incoming request.
+ *
+ * @param request the incoming request to process.
+ * @param response the outgoing response to provide.
+ */
+ public void process( DavServerRequest request, HttpServletResponse response )
+ throws DavServerException, ServletException, IOException;
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java
new file mode 100644
index 000000000..893f059ef
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+/**
+ * DavServerException
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DavServerException.java 5379 2007-01-07 22:54:41Z joakime $
+ */
+public class DavServerException
+ extends Exception
+{
+
+ public DavServerException()
+ {
+ }
+
+ public DavServerException( String message )
+ {
+ super( message );
+ }
+
+ public DavServerException( Throwable cause )
+ {
+ super( cause );
+ }
+
+ public DavServerException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java
new file mode 100644
index 000000000..251cee6ad
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+/**
+ * DavServerListener
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DavServerListener.java 5379 2007-01-07 22:54:41Z joakime $
+ */
+public interface DavServerListener
+{
+ public void serverCollectionCreated( DavServerComponent server, String resource );
+
+ public void serverCollectionRemoved( DavServerComponent server, String resource );
+
+ public void serverResourceCreated( DavServerComponent server, String resource );
+
+ public void serverResourceRemoved( DavServerComponent server, String resource );
+
+ public void serverResourceModified( DavServerComponent server, String resource );
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java
new file mode 100644
index 000000000..7c86cd533
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * DavServerManager
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DavServerManager.java 6017 2007-03-06 00:39:53Z joakime $
+ */
+public interface DavServerManager
+{
+ /** The Plexus ROLE name. */
+ public static final String ROLE = DavServerManager.class.getName();
+
+ /**
+ * Create a DavServerComponent and start tracking it.
+ *
+ * @param prefix the prefix for this component.
+ * @param rootDirectory the root directory for this component's content. null to not set a root directory.
+ * @return the created component, suitable for use.
+ * @throws DavServerException
+ */
+ public DavServerComponent createServer( String prefix, File rootDirectory ) throws DavServerException;
+
+ /**
+ * Get the collection of tracked servers.
+ *
+ * @return Collection of {@link DavServerComponent} objects.
+ */
+ public Collection getServers();
+
+ /**
+ * Removes a specific server from the tracked list of servers.
+ *
+ * NOTE: This does not remove the associated files on disk, merely the reference being tracked.
+ *
+ * @param prefix the prefix to remove.
+ */
+ public void removeServer( String prefix );
+
+ /**
+ * Get the {@link DavServerComponent} associated with the specified prefix.
+ *
+ * @param prefix the prefix for the dav server component to use.
+ * @return the DavServerComponent, or null if not found.
+ */
+ public DavServerComponent getServer( String prefix );
+
+ /**
+ * Remove all servers being tracked by the manager.
+ */
+ public void removeAllServers();
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java
new file mode 100644
index 000000000..b66edee3f
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * DefaultDavServerManager
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DefaultDavServerManager.java 7009 2007-10-25 23:34:43Z joakime $
+ *
+ * @plexus.component role="org.apache.maven.archiva.webdav.DavServerManager" role-hint="default"
+ */
+public class DefaultDavServerManager
+ implements DavServerManager
+{
+ /**
+ * @plexus.requirement role-hint="simple"
+ */
+ private DavServerComponent server;
+
+ private Map servers;
+
+ public DefaultDavServerManager()
+ {
+ servers = new HashMap();
+ }
+
+ public DavServerComponent createServer( String prefix, File rootDirectory )
+ throws DavServerException
+ {
+ if ( servers.containsKey( prefix ) )
+ {
+ throw new DavServerException( "Unable to create a new server on a pre-existing prefix [" + prefix + "]" );
+ }
+
+ server.setPrefix( prefix );
+ if ( rootDirectory != null )
+ {
+ server.setRootDirectory( rootDirectory );
+ }
+
+ servers.put( prefix, server );
+
+ return server;
+ }
+
+ public DavServerComponent getServer( String prefix )
+ {
+ return (DavServerComponent) servers.get( prefix );
+ }
+
+ public void removeServer( String prefix )
+ {
+ servers.remove( prefix );
+ }
+
+ public Collection getServers()
+ {
+ return servers.values();
+ }
+
+ public void removeAllServers()
+ {
+ servers.clear();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java
new file mode 100644
index 000000000..b654f359a
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.archiva.webdav.DavServerManager;
+import org.codehaus.plexus.spring.PlexusToSpringUtils;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * AbstractWebDavServlet
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractWebDavServlet.java 7009 2007-10-25 23:34:43Z joakime $
+ */
+public abstract class AbstractWebDavServlet
+ extends HttpServlet
+{
+ public static final String INIT_USE_INDEX_HTML = "dav.use.index.html";
+
+ private boolean debug = false;
+
+ protected DavServerManager davManager;
+
+ public String getServletInfo()
+ {
+ return "Plexus WebDAV Servlet";
+ }
+
+ public void init( ServletConfig config )
+ throws ServletException
+ {
+ super.init( config );
+
+ WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( config.getServletContext() );
+ davManager = (DavServerManager) wac.getBean( PlexusToSpringUtils.buildSpringId( DavServerManager.ROLE ) );
+ if ( davManager == null )
+ {
+ throw new ServletException( "Unable to lookup davManager" );
+ }
+ }
+
+ /**
+ * Perform any authentication steps here.
+ *
+ * If authentication fails, it is the responsibility of the implementor to issue
+ * the appropriate status codes and/or challenge back on the response object, then
+ * return false on the overridden version of this method.
+ *
+ * To effectively not have authentication, just implement this method and always
+ * return true.
+ *
+ * @param davRequest the incoming dav request.
+ * @param httpResponse the outgoing http response.
+ * @return true if user is authenticated, false if not.
+ * @throws ServletException if there was a problem performing authencation.
+ * @throws IOException if there was a problem obtaining credentials or issuing challenge.
+ */
+ public boolean isAuthenticated( DavServerRequest davRequest, HttpServletResponse httpResponse )
+ throws ServletException, IOException
+ {
+ // Always return true. Effectively no Authentication done.
+ return true;
+ }
+
+ /**
+ * Perform any authorization steps here.
+ *
+ * If authorization fails, it is the responsibility of the implementor to issue
+ * the appropriate status codes and/or challenge back on the response object, then
+ * return false on the overridden version of this method.
+ *
+ * to effectively not have authorization, just implement this method and always
+ * return true.
+ *
+ * @param davRequest
+ * @param httpResponse
+ * @return
+ * @throws ServletException
+ * @throws IOException
+ */
+ public boolean isAuthorized( DavServerRequest davRequest, HttpServletResponse httpResponse )
+ throws ServletException, IOException
+ {
+ // Always return true. Effectively no Authorization done.
+ return true;
+ }
+
+ public boolean isDebug()
+ {
+ return debug;
+ }
+
+ public void setDebug( boolean debug )
+ {
+ this.debug = debug;
+ }
+
+ protected void requestDebug( HttpServletRequest request )
+ {
+ if ( debug )
+ {
+ System.out.println( "-->>> request ----------------------------------------------------------" );
+ System.out.println( "--> " + request.getScheme() + "://" + request.getServerName() + ":"
+ + request.getServerPort() + request.getServletPath() );
+ System.out.println( request.getMethod() + " " + request.getRequestURI()
+ + ( request.getQueryString() != null ? "?" + request.getQueryString() : "" ) + " " + "HTTP/1.1" );
+
+ Enumeration enHeaders = request.getHeaderNames();
+ while ( enHeaders.hasMoreElements() )
+ {
+ String headerName = (String) enHeaders.nextElement();
+ String headerValue = request.getHeader( headerName );
+ System.out.println( headerName + ": " + headerValue );
+ }
+
+ System.out.println();
+
+ System.out.println( "------------------------------------------------------------------------" );
+ }
+ }
+
+ public abstract void setUseIndexHtml( boolean useIndexHtml );
+
+ public boolean getUseIndexHtml( ServletConfig config )
+ throws ServletException
+ {
+ String useIndexHtml = config.getInitParameter( INIT_USE_INDEX_HTML );
+
+ if ( StringUtils.isEmpty( useIndexHtml ) )
+ {
+ return false;
+ }
+
+ return BooleanUtils.toBoolean( useIndexHtml );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java
new file mode 100644
index 000000000..913e5a7f8
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet;
+
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+
+/**
+ * DavServerRequest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: DavServerRequest.java 7073 2007-11-22 04:04:50Z brett $
+ */
+public interface DavServerRequest
+{
+ public String getPrefix();
+
+ public String getLogicalResource();
+
+ public void setLogicalResource( String logicalResource );
+
+ public WrappedRepositoryRequest getRequest();
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java
new file mode 100644
index 000000000..3bbdc1703
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet.basic;
+
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+
+/**
+ * BasicDavServerRequest - for requests that have a prefix based off of the servlet path id.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: BasicDavServerRequest.java 7073 2007-11-22 04:04:50Z brett $
+ */
+public class BasicDavServerRequest
+ implements DavServerRequest
+{
+ private WrappedRepositoryRequest request;
+
+ private String prefix;
+
+ private String logicalResource;
+
+ public BasicDavServerRequest( WrappedRepositoryRequest request )
+ {
+ this.request = request;
+ this.prefix = request.getServletPath();
+ this.logicalResource = request.getPathInfo();
+ }
+
+ public void setLogicalResource( String logicalResource )
+ {
+ this.logicalResource = logicalResource;
+ this.request.setPathInfo( logicalResource );
+ }
+
+ public String getLogicalResource()
+ {
+ return this.logicalResource;
+ }
+
+ public String getPrefix()
+ {
+ return this.prefix;
+ }
+
+ public WrappedRepositoryRequest getRequest()
+ {
+ return request;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java
new file mode 100644
index 000000000..49193e31f
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet.basic;
+
+import org.apache.maven.archiva.webdav.DavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.servlet.AbstractWebDavServlet;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+import org.codehaus.plexus.util.StringUtils;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * BasicWebDavServlet - Basic implementation of a single WebDAV server as servlet.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: BasicWebDavServlet.java 6017 2007-03-06 00:39:53Z joakime $
+ */
+public class BasicWebDavServlet
+ extends AbstractWebDavServlet
+{
+ public static final String INIT_ROOT_DIRECTORY = "dav.root";
+
+ private DavServerComponent davServer;
+
+ // -----------------------------------------------------------------------
+ // Servlet Implementation
+ // -----------------------------------------------------------------------
+
+ public void init( ServletConfig config )
+ throws ServletException
+ {
+ super.init( config );
+
+ String prefix = config.getServletName();
+
+ boolean useIndexHtml = getUseIndexHtml( config );
+ File rootDir = getRootDirectory( config );
+
+ if ( rootDir != null && !rootDir.isDirectory() )
+ {
+ log( "Invalid configuration, the dav root " + rootDir.getPath()
+ + " is not a directory: [" + rootDir.getAbsolutePath() + "]" );
+ }
+
+ try
+ {
+ davServer = davManager.createServer( prefix, rootDir );
+ davServer.setUseIndexHtml( useIndexHtml );
+ davServer.init( config );
+ }
+ catch ( DavServerException e )
+ {
+ throw new ServletException( "Unable to create DAV Server component for prefix [" + prefix
+ + "] mapped to root directory [" + rootDir.getPath() + "]", e );
+ }
+ }
+
+ public File getRootDirectory( ServletConfig config )
+ throws ServletException
+ {
+ String rootDirName = config.getInitParameter( INIT_ROOT_DIRECTORY );
+
+ if ( StringUtils.isEmpty( rootDirName ) )
+ {
+ log( "Init Parameter '" + INIT_ROOT_DIRECTORY + "' is empty." );
+ return null;
+ }
+
+ return new File( rootDirName );
+ }
+
+ protected void service( HttpServletRequest httpRequest, HttpServletResponse httpResponse )
+ throws ServletException, IOException
+ {
+ DavServerRequest davRequest = new BasicDavServerRequest( new WrappedRepositoryRequest( httpRequest ) );
+
+ if ( davServer == null )
+ {
+ throw new ServletException( "Unable to service DAV request due to unconfigured DavServerComponent." );
+ }
+
+ requestDebug( httpRequest );
+
+ if ( !isAuthenticated( davRequest, httpResponse ) )
+ {
+ return;
+ }
+
+ if ( !isAuthorized( davRequest, httpResponse ) )
+ {
+ return;
+ }
+
+ try
+ {
+ davServer.process( davRequest, httpResponse );
+ }
+ catch ( DavServerException e )
+ {
+ throw new ServletException( "Unable to process request.", e );
+ }
+ }
+
+ public void setUseIndexHtml( boolean useIndexHtml )
+ {
+ davServer.setUseIndexHtml( useIndexHtml );
+ }
+
+ public DavServerComponent getDavServer()
+ {
+ return davServer;
+ }
+
+ public void setDavServer( DavServerComponent davServer )
+ {
+ this.davServer = davServer;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java
new file mode 100644
index 000000000..4d9ec43cb
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet.multiplexed;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+
+/**
+ * <p/>
+ * MultiplexedDavServerRequest - For requests that contain the server prefix information within the requested
+ * servlet's pathInfo parameter (as the first path entry).
+ * </p>
+ * <p/>
+ * <p/>
+ * You would use this dav server request object when you are working with a single servlet that is handling
+ * multiple dav server components.
+ * </p>
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: MultiplexedDavServerRequest.java 7073 2007-11-22 04:04:50Z brett $
+ */
+public class MultiplexedDavServerRequest
+ implements DavServerRequest
+{
+ private WrappedRepositoryRequest request;
+
+ private String prefix;
+
+ private String logicalResource;
+
+ public MultiplexedDavServerRequest( WrappedRepositoryRequest request )
+ {
+ String requestPathInfo = StringUtils.defaultString( request.getPathInfo() );
+
+ // Remove prefixing slash as the repository id doesn't contain it;
+ if ( requestPathInfo.startsWith( "/" ) )
+ {
+ requestPathInfo = requestPathInfo.substring( 1 );
+ }
+
+ // Find first element, if slash exists.
+ int slash = requestPathInfo.indexOf( '/' );
+ if ( slash > 0 )
+ {
+ // Filtered: "central/org/apache/maven/" -> "central"
+ this.prefix = requestPathInfo.substring( 0, slash );
+
+ this.logicalResource = requestPathInfo.substring( slash );
+
+ if ( this.logicalResource.endsWith( "/.." ) )
+ {
+ this.logicalResource += "/";
+ }
+
+ /* Perform a simple security normalization of the requested pathinfo.
+ * This is to prevent requests for information outside of the root directory.
+ */
+ this.logicalResource = FilenameUtils.normalize( logicalResource );
+
+ if ( logicalResource != null && logicalResource.startsWith( "//" ) )
+ {
+ logicalResource = logicalResource.substring( 1 );
+ }
+
+ if ( this.logicalResource == null )
+ {
+ this.logicalResource = "/";
+ }
+ }
+ else
+ {
+ this.prefix = requestPathInfo;
+ this.logicalResource = "/";
+ }
+
+ this.request = request;
+ this.request.setPathInfo( logicalResource );
+ }
+
+ public void setLogicalResource( String logicalResource )
+ {
+ this.logicalResource = logicalResource;
+ this.request.setPathInfo( logicalResource );
+ }
+
+ public String getLogicalResource()
+ {
+ return this.logicalResource;
+ }
+
+ public String getPrefix()
+ {
+ return this.prefix;
+ }
+
+ public WrappedRepositoryRequest getRequest()
+ {
+ return request;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java
new file mode 100644
index 000000000..19d03093b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet.multiplexed;
+
+import org.apache.maven.archiva.webdav.DavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.DavServerManager;
+import org.apache.maven.archiva.webdav.servlet.AbstractWebDavServlet;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.Iterator;
+
+/**
+ * <p>
+ * MultiplexedWebDavServlet - and abstracted multiplexed webdav servlet.
+ * </p>
+ *
+ * <p>
+ * Implementations of this servlet should override the {@link #initServers} method and create all of the
+ * appropriate DavServerComponents needed using the {@link DavServerManager} obtained via the {@link #getDavManager()}
+ * method.
+ * </p>
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: MultiplexedWebDavServlet.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public abstract class MultiplexedWebDavServlet
+ extends AbstractWebDavServlet
+{
+ private boolean useIndexHtml = false;
+
+ public void init( ServletConfig config )
+ throws ServletException
+ {
+ super.init( config );
+
+ this.useIndexHtml = getUseIndexHtml( config );
+
+ try
+ {
+ initServers( config );
+ }
+ catch ( DavServerException e )
+ {
+ throw new ServletException( e );
+ }
+ }
+
+ /**
+ * Create any DavServerComponents here.
+ * Use the {@link #createServer(String, File, ServletConfig)} method to create your servers.
+ *
+ * @param config the config to use.
+ * @throws DavServerException if there was a problem initializing the server components.
+ */
+ public abstract void initServers( ServletConfig config )
+ throws DavServerException;
+
+ public DavServerComponent createServer( String prefix, File rootDirectory, ServletConfig config )
+ throws DavServerException
+ {
+ DavServerComponent serverComponent = davManager.createServer( prefix, rootDirectory );
+ serverComponent.setUseIndexHtml( useIndexHtml );
+ serverComponent.init( config );
+ return serverComponent;
+ }
+
+ protected void service( HttpServletRequest httpRequest, HttpServletResponse httpResponse )
+ throws ServletException, IOException
+ {
+ DavServerRequest davRequest = new MultiplexedDavServerRequest( new WrappedRepositoryRequest( httpRequest ) );
+
+ DavServerComponent davServer = davManager.getServer( davRequest.getPrefix() );
+
+ if ( davServer == null )
+ {
+ String errorMessage = "[" + davRequest.getPrefix() + "] Not Found (Likely Unconfigured).";
+ httpResponse.sendError( HttpURLConnection.HTTP_NOT_FOUND, errorMessage );
+ return;
+ }
+
+ requestDebug( httpRequest );
+
+ if ( !isAuthenticated( davRequest, httpResponse ) )
+ {
+ return;
+ }
+
+ if ( !isAuthorized( davRequest, httpResponse ) )
+ {
+ return;
+ }
+
+ try
+ {
+ davServer.process( davRequest, httpResponse );
+ }
+ catch ( DavServerException e )
+ {
+ throw new ServletException( "Unable to process request.", e );
+ }
+ }
+
+ public void setUseIndexHtml( boolean useIndexHtml )
+ {
+ for ( Iterator it = davManager.getServers().iterator(); it.hasNext(); )
+ {
+ DavServerComponent davServer = (DavServerComponent) it.next();
+ davServer.setUseIndexHtml( useIndexHtml );
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java
new file mode 100644
index 000000000..736a3c07e
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVMultiStatus;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * HackedMoveMethod - Created to address the needs for inter-repository moves.
+ *
+ * @author Pier Fumagalli (Original it.could.webdav 0.4 version)
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a> (Hacked Version)
+ * @version $Id: HackedMoveMethod.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public class HackedMoveMethod
+ implements DAVMethod
+{
+
+ public HackedMoveMethod()
+ {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>MOVE</code> method.</p>
+ */
+ public void process( DAVTransaction transaction, DAVResource resource )
+ throws IOException
+ {
+ URI target = transaction.getDestination();
+ if ( target == null )
+ throw new DAVException( 412, "No destination" );
+
+ if ( target.getScheme() == null )
+ {
+ // This is a relative file system destination target.
+ DAVResource dest = resource.getRepository().getResource( target );
+ moveWithinRepository( transaction, resource, dest );
+ }
+ else
+ {
+ // This is a inter-repository move request.
+ URI dest = target;
+ moveInterRepository( transaction, resource, dest );
+ }
+ }
+
+ private void moveInterRepository( DAVTransaction transaction, DAVResource resource, URI dest )
+ throws DAVException
+ {
+ /* TODO: Figure out how to handle a Repository to Repository MOVE of content, and still maintain
+ * the security credentials from the original request. (Need to support NTLM, Digest, BASIC)
+ *
+ * IDEA: Could support non-secured Webdav Destination using slide client libraries.
+ */
+ transaction.setStatus( 501 );
+ throw new DAVException( 501, "Server side MOVE to external WebDAV instance not supported." );
+ }
+
+ private void moveWithinRepository( DAVTransaction transaction, DAVResource resource, DAVResource dest )
+ throws IOException
+ {
+ int depth = transaction.getDepth();
+ boolean recursive = false;
+ if ( depth == 0 )
+ {
+ recursive = false;
+ }
+ else if ( depth == DAVTransaction.INFINITY )
+ {
+ recursive = true;
+ }
+ else
+ {
+ throw new DAVException( 412, "Invalid Depth specified" );
+ }
+
+ try
+ {
+ int status;
+ if ( !dest.isNull() && !transaction.getOverwrite() )
+ {
+ status = 412; // MOVE-on-existing should fail with 412
+ }
+ else
+ {
+ resource.copy( dest, transaction.getOverwrite(), recursive );
+ resource.delete();
+
+ if ( transaction.getOverwrite() )
+ {
+ status = 204; // No Content
+ }
+ else
+ {
+ status = 201; // Created
+ }
+ }
+ transaction.setStatus( status );
+ }
+ catch ( DAVMultiStatus multistatus )
+ {
+ multistatus.write( transaction );
+ }
+ }
+
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java
new file mode 100644
index 000000000..3eee8786b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import it.could.util.StreamTools;
+import it.could.webdav.DAVException;
+import it.could.webdav.DAVInputStream;
+import it.could.webdav.DAVMethod;
+import it.could.webdav.DAVNotModified;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+import it.could.webdav.DAVUtilities;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.archiva.webdav.util.MimeTypes;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * ReplacementGetMethod
+ *
+ * @author Pier Fumagalli (Original it.could.webdav 0.4 version)
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a> (Replacement Version)
+ * @version $Id: ReplacementGetMethod.java 7002 2007-10-23 22:40:37Z joakime $
+ *
+ * @plexus.component
+ * role="it.could.webdav.DAVMethod"
+ * role-hint="get-with-indexing"
+ */
+public class ReplacementGetMethod implements DAVMethod
+{
+ /** <p>The encoding charset to repsesent collections.</p> */
+ public static final String ENCODING = "UTF-8";
+
+ /** <p>The mime type that {@link ReplacementGetMethod} will use serving index.html files.</p> */
+ public static final String HTML_MIME_TYPE = "text/html";
+
+ /** <p>The mime type that {@link ReplacementGetMethod} will use serving collections.</p> */
+ public static final String COLLECTION_MIME_TYPE = HTML_MIME_TYPE + "; charset=\"" + ENCODING + "\"";
+
+ /** <p>The header for content disposition.</p> */
+ public static final String CONTENT_DISPOSITION = "Content-Disposition";
+
+ /** <p>The content-disposition for fancy-indexing.</p> */
+ public static final String INLINE_INDEX_HTML = "inline; filename=\"index.html\"";
+
+ /**
+ * @plexus.requirement
+ */
+ private MimeTypes mimeTypes;
+
+ private boolean useIndexHtml = false;
+
+ /**
+ * <p>Create a new {@link ReplacementGetMethod} instance.</p>
+ */
+ public ReplacementGetMethod()
+ {
+ super();
+ }
+
+ /**
+ * <p>Process the <code>GET</code> method.</p>
+ */
+ public void process( DAVTransaction transaction, DAVResource resource ) throws IOException
+ {
+ // Handle boilerplate
+ if ( resource.isNull() )
+ throw new DAVException( 404, "Not found", resource );
+
+ notModified( transaction, resource );
+
+ copyHeaders( transaction, resource );
+
+ // Process the request.
+ final String originalPath = transaction.getOriginalPath();
+ final String normalizedPath = transaction.getNormalizedPath();
+ final String current;
+ final String parent;
+
+ if ( originalPath.equals( normalizedPath ) )
+ {
+ final String relativePath = resource.getRelativePath();
+ if ( relativePath.equals( "" ) )
+ {
+ current = transaction.lookup( resource ).toASCIIString();
+ }
+ else
+ {
+ current = relativePath;
+ }
+ parent = "./";
+ }
+ else
+ {
+ current = "./";
+ parent = "../";
+ }
+
+ if ( resource.isCollection() )
+ {
+ DAVResource indexHtml = null;
+
+ if ( useIndexHtml )
+ {
+ for ( Iterator it = resource.getChildren(); it.hasNext(); )
+ {
+ DAVResource child = (DAVResource) it.next();
+ String name = child.getDisplayName().toLowerCase();
+ if ( StringUtils.equals( "index.html", name ) || StringUtils.equals( "index.htm", name ) )
+ {
+ indexHtml = child;
+ break;
+ }
+ }
+ }
+
+ if ( useIndexHtml && indexHtml != null )
+ {
+ transaction.setContentType( COLLECTION_MIME_TYPE );
+ transaction.setHeader( CONTENT_DISPOSITION, INLINE_INDEX_HTML );
+ sendResource( transaction, indexHtml );
+ }
+ else
+ {
+ transaction.setContentType( COLLECTION_MIME_TYPE );
+ transaction.setHeader( CONTENT_DISPOSITION, INLINE_INDEX_HTML );
+ sendFancyIndex( transaction, resource, current, parent );
+ }
+ }
+ else
+ {
+ /* Processing a normal resource request */
+ transaction.setContentType( mimeTypes.getMimeType( resource.getDisplayName() ) );
+ transaction.setHeader( CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getDisplayName() + "\"" );
+ sendResource( transaction, resource );
+ }
+ }
+
+ private void copyHeaders( DAVTransaction transaction, DAVResource resource )
+ {
+ /* Get the headers of this method */
+ String ctyp = resource.getContentType();
+ String etag = resource.getEntityTag();
+ String lmod = DAVUtilities.formatHttpDate( resource.getLastModified() );
+ String clen = DAVUtilities.formatNumber( resource.getContentLength() );
+
+ /* Set the normal headers that are required for a GET */
+ if ( ctyp != null )
+ {
+ transaction.setContentType( ctyp );
+ }
+
+ if ( etag != null )
+ {
+ transaction.setHeader( "ETag", etag );
+ }
+
+ if ( lmod != null )
+ {
+ transaction.setHeader( "Last-Modified", lmod );
+ }
+
+ if ( clen != null )
+ {
+ transaction.setHeader( "Content-Length", clen );
+ }
+ }
+
+ private void sendResource( DAVTransaction transaction, DAVResource resource ) throws IOException
+ {
+ OutputStream out = null;
+ DAVInputStream in = null;
+
+ try
+ {
+ out = transaction.write();
+ in = resource.read();
+
+ byte buffer[] = new byte[4096 * 16];
+ int k = -1;
+ while ( ( k = in.read( buffer ) ) != -1 )
+ {
+ out.write( buffer, 0, k );
+ }
+
+ out.flush();
+ }
+ finally
+ {
+ StreamTools.close( in );
+ StreamTools.close( out );
+ }
+ }
+
+ private void sendFancyIndex( DAVTransaction transaction, DAVResource resource, final String current,
+ final String parent ) throws IOException
+ {
+ PrintWriter out = transaction.write( ENCODING );
+ String path = resource.getRelativePath();
+ out.println( "<html>" );
+ out.println( "<head>" );
+ out.println( "<title>Collection: /" + path + "</title>" );
+ out.println( "</head>" );
+ out.println( "<body>" );
+ out.println( "<h2>Collection: /" + path + "</h2>" );
+ out.println( "<ul>" );
+
+ /* Process the parent */
+ final DAVResource parentResource = resource.getParent();
+ if ( parentResource != null )
+ {
+ out.print( "<li><a href=\"" );
+ out.print( parent );
+ out.print( "\">" );
+ out.print( parentResource.getDisplayName() );
+ out.println( "</a> <i><small>(Parent)</small></i></li>" );
+ out.println( "</ul>" );
+ out.println( "<ul>" );
+ }
+
+ /* Process the children (in two sorted sets, for nice ordering) */
+ Set resources = new TreeSet();
+ Set collections = new TreeSet();
+ Iterator iterator = resource.getChildren();
+ while ( iterator.hasNext() )
+ {
+ final DAVResource child = (DAVResource) iterator.next();
+ final StringBuffer buffer = new StringBuffer();
+ final String childPath = child.getDisplayName();
+ buffer.append( "<li><a href=\"" );
+ buffer.append( current );
+ buffer.append( childPath );
+ buffer.append( "\">" );
+ buffer.append( childPath );
+ buffer.append( "</li>" );
+ if ( child.isCollection() )
+ {
+ collections.add( buffer.toString() );
+ }
+ else
+ {
+ resources.add( buffer.toString() );
+ }
+ }
+
+ /* Spit out the collections first and the resources then */
+ for ( Iterator i = collections.iterator(); i.hasNext(); )
+ out.println( i.next() );
+ for ( Iterator i = resources.iterator(); i.hasNext(); )
+ out.println( i.next() );
+
+ out.println( "</ul>" );
+ out.println( "</body>" );
+ out.println( "</html>" );
+ out.flush();
+ }
+
+ private void notModified( DAVTransaction transaction, DAVResource resource )
+ {
+ Date ifmod = transaction.getIfModifiedSince();
+ Date lsmod = resource.getLastModified();
+ if ( resource.isResource() && ( ifmod != null ) && ( lsmod != null ) )
+ {
+ /* HTTP doesn't send milliseconds, but Java does, so, reset them */
+ lsmod = new Date( ( (long) ( lsmod.getTime() / 1000 ) ) * 1000 );
+ if ( !ifmod.before( lsmod ) )
+ throw new DAVNotModified( resource );
+ }
+ }
+
+ public boolean isUseIndexHtml()
+ {
+ return useIndexHtml;
+ }
+
+ public void setUseIndexHtml( boolean useIndexHtml )
+ {
+ this.useIndexHtml = useIndexHtml;
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java
new file mode 100644
index 000000000..af6794faf
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import it.could.webdav.DAVListener;
+import it.could.webdav.DAVProcessor;
+import it.could.webdav.DAVRepository;
+import it.could.webdav.DAVResource;
+import it.could.webdav.DAVTransaction;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.archiva.webdav.AbstractDavServerComponent;
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.servlet.DavServerRequest;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * SimpleDavServerComponent
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: SimpleDavServerComponent.java 7097 2007-11-30 12:57:29Z handyande $
+ *
+ * @plexus.component role="org.apache.maven.archiva.webdav.DavServerComponent"
+ * role-hint="simple"
+ * instantiation-strategy="per-lookup"
+ */
+public class SimpleDavServerComponent
+ extends AbstractDavServerComponent
+ implements DAVListener
+{
+ /**
+ * @plexus.requirement
+ * role="it.could.webdav.DAVMethod"
+ * role-hint="get-with-indexing"
+ */
+ public ReplacementGetMethod methodGet;
+
+ private String prefix;
+
+ private File rootDirectory;
+
+ private DAVRepository davRepository;
+
+ private DAVProcessor davProcessor;
+
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ public File getRootDirectory()
+ {
+ return rootDirectory;
+ }
+
+ public void setPrefix( String prefix )
+ {
+ this.prefix = prefix;
+ }
+
+ public void setRootDirectory( File rootDirectory )
+ {
+ this.rootDirectory = rootDirectory;
+ }
+
+ public void init( ServletConfig servletConfig )
+ throws DavServerException
+ {
+ servletConfig.getServletContext().log( "Initializing " + this.getClass().getName() );
+ try
+ {
+ davRepository = new DAVRepository( rootDirectory );
+ davProcessor = new DAVProcessor( davRepository );
+ davRepository.addListener( this );
+
+ hackDavProcessor( davProcessor );
+ }
+ catch ( IOException e )
+ {
+ throw new DavServerException( "Unable to initialize DAVRepository.", e );
+ }
+ }
+
+ /**
+ * Replace the problematic dav methods with local hacked versions.
+ *
+ * @param davProcessor
+ * @throws DavServerException
+ */
+ private void hackDavProcessor( DAVProcessor davProcessor )
+ throws DavServerException
+ {
+ davProcessor.setMethod( "MOVE", new HackedMoveMethod() );
+ davProcessor.setMethod( "GET", methodGet );
+
+ /* Reflection based technique.
+ try
+ {
+ Field fldInstance = davProcessor.getClass().getDeclaredField( "INSTANCES" );
+ fldInstance.setAccessible( true );
+
+ Map mapInstances = (Map) fldInstance.get( davProcessor );
+
+ // Replace MOVE method.
+ // TODO: Remove MOVE method when upgrading it.could.webdav to v0.5
+ mapInstances.put( "MOVE", (DAVMethod) new HackedMoveMethod() );
+
+ // Replace GET method.
+ mapInstances.put( "GET", (DAVMethod) methodGet );
+ }
+ catch ( Throwable e )
+ {
+ throw new DavServerException( "Unable to twiddle DAVProcessor.INSTANCES field.", e );
+ }
+ */
+ }
+
+ public void process( DavServerRequest request, HttpServletResponse response )
+ throws ServletException, IOException
+ {
+ DAVTransaction transaction = new DAVTransaction( request.getRequest(), response );
+
+ /* BEGIN - it.could.webdav hacks
+ * TODO: Remove hacks with release of it.could.webdav 0.5 (or newer)
+ */
+ String depthValue = request.getRequest().getHeader( "Depth" );
+ if ( StringUtils.equalsIgnoreCase( "infinity", depthValue ) )
+ {
+ // See - http://could.it/bugs/browse/DAV-3
+ request.getRequest().setHeader( "Depth", "infinity" );
+ }
+ /* END - it.could.webdav hacks */
+
+ davProcessor.process( transaction );
+ }
+
+ public void notify( DAVResource resource, int event )
+ {
+ switch ( event )
+ {
+ case DAVListener.COLLECTION_CREATED:
+ triggerCollectionCreated( resource.getRelativePath() );
+ break;
+ case DAVListener.COLLECTION_REMOVED:
+ triggerCollectionRemoved( resource.getRelativePath() );
+ break;
+ case DAVListener.RESOURCE_CREATED:
+ triggerResourceCreated( resource.getRelativePath() );
+ break;
+ case DAVListener.RESOURCE_REMOVED:
+ triggerResourceRemoved( resource.getRelativePath() );
+ break;
+ case DAVListener.RESOURCE_MODIFIED:
+ triggerResourceModified( resource.getRelativePath() );
+ break;
+ }
+ }
+
+ public void setUseIndexHtml( boolean useIndexHtml )
+ {
+ super.setUseIndexHtml( useIndexHtml );
+ this.methodGet.setUseIndexHtml( useIndexHtml );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java
new file mode 100644
index 000000000..1219e712e
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.util;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * MimeTypes
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: MimeTypes.java 7010 2007-10-25 23:35:02Z joakime $
+ *
+ * @plexus.component role="org.apache.maven.archiva.webdav.util.MimeTypes"
+ */
+public class MimeTypes
+ extends AbstractLogEnabled
+ implements Initializable
+{
+ /**
+ * @plexus.configuration default-value="org/apache/maven/archiva/webdav/util/mime-types.txt"
+ */
+ private String resource;
+
+ private Map mimeMap = new HashMap();
+
+ /**
+ * Get the Mime Type for the provided filename.
+ *
+ * @param filename the filename to obtain the mime type for.
+ * @return a mime type String, or null if filename is null, has no extension, or no mime type is associated with it.
+ */
+ public String getMimeType( String filename )
+ {
+ String value = null;
+ if ( !StringUtils.isEmpty( filename ) )
+ {
+ int index = filename.lastIndexOf( '.' );
+
+ if ( index >= 0 )
+ {
+ value = (String) mimeMap.get( filename.substring( index + 1 ).toLowerCase() );
+ }
+ }
+ return value;
+
+ }
+
+ public void initialize()
+ throws InitializationException
+ {
+ load( resource );
+ }
+
+ public void load( File file )
+ {
+ if ( !file.exists() || !file.isFile() || !file.canRead() )
+ {
+ getLogger().error( "Unable to load mime types from file " + file.getAbsolutePath() + " : not a readable file." );
+ return;
+ }
+
+ FileInputStream fis = null;
+
+ try
+ {
+ fis = new FileInputStream( file );
+ }
+ catch ( FileNotFoundException e )
+ {
+ getLogger().error( "Unable to load mime types from file " + file.getAbsolutePath() + " : " + e.getMessage(), e );
+ }
+ finally
+ {
+ IOUtils.closeQuietly( fis );
+ }
+ }
+
+ public void load( String resourceName )
+ {
+ ClassLoader cloader = this.getClass().getClassLoader();
+
+ /* Load up the mime types table */
+ URL mimeURL = cloader.getResource( resourceName );
+
+ if ( mimeURL == null )
+ {
+ throw new IllegalStateException( "Unable to find resource " + resourceName );
+ }
+
+ InputStream mimeStream = null;
+
+ try
+ {
+ mimeStream = mimeURL.openStream();
+ load( mimeStream );
+ }
+ catch ( IOException e )
+ {
+ getLogger().error( "Unable to load mime map " + resourceName + " : " + e.getMessage(), e );
+ }
+ finally
+ {
+ IOUtils.closeQuietly( mimeStream );
+ }
+ }
+
+ public void load( InputStream mimeStream )
+ {
+ mimeMap.clear();
+
+ InputStreamReader reader = null;
+ BufferedReader buf = null;
+
+ try
+ {
+ reader = new InputStreamReader( mimeStream );
+ buf = new BufferedReader( reader );
+ String line = null;
+
+ while ( ( line = buf.readLine() ) != null )
+ {
+ line = line.trim();
+
+ if ( line.length() == 0 )
+ {
+ // empty line. skip it
+ continue;
+ }
+
+ if ( line.startsWith( "#" ) )
+ {
+ // Comment. skip it
+ continue;
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer( line );
+ if ( tokenizer.countTokens() > 1 )
+ {
+ String type = tokenizer.nextToken();
+ while ( tokenizer.hasMoreTokens() )
+ {
+ String extension = tokenizer.nextToken().toLowerCase();
+ this.mimeMap.put( extension, type );
+ }
+ }
+ }
+ }
+ catch ( IOException e )
+ {
+ getLogger().error( "Unable to read mime types from input stream : " + e.getMessage(), e );
+ }
+ finally
+ {
+ IOUtils.closeQuietly( buf );
+ IOUtils.closeQuietly( reader );
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java
new file mode 100644
index 000000000..a551e8c17
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.util;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WebdavMethodUtil
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: WebdavMethodUtil.java 5412 2007-01-13 01:18:47Z joakime $
+ */
+public class WebdavMethodUtil
+{
+ private static final List READ_METHODS;
+
+ static
+ {
+ READ_METHODS = new ArrayList();
+ READ_METHODS.add( "HEAD" );
+ READ_METHODS.add( "GET" );
+ READ_METHODS.add( "PROPFIND" );
+ READ_METHODS.add( "OPTIONS" );
+ READ_METHODS.add( "REPORT" );
+ }
+
+ public static boolean isReadMethod( String method )
+ {
+ if ( StringUtils.isBlank( method ) )
+ {
+ return false;
+ }
+
+ return READ_METHODS.contains( method.toUpperCase() );
+ }
+
+ public static boolean isWriteMethod( String method )
+ {
+ if ( StringUtils.isBlank( method ) )
+ {
+ return false;
+ }
+
+ return !READ_METHODS.contains( method.toUpperCase() );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java
new file mode 100644
index 000000000..29ef2f556
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.util;
+
+import org.apache.commons.lang.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * RepositoryRequest - wrapped servlet request to adjust the incoming request before the components get it.
+ * It eliminates the prefix from the pathInfo portion of the URL requested.
+ * And also allows for Header adjustment.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: WrappedRepositoryRequest.java 7001 2007-10-23 22:40:14Z joakime $
+ */
+public class WrappedRepositoryRequest
+ extends HttpServletRequestWrapper
+{
+ private String pathInfo;
+
+ private Map headers;
+
+ /**
+ * The Date Formats most commonly seen in Request Headers.
+ */
+ private SimpleDateFormat dateFormats[];
+
+ public WrappedRepositoryRequest( HttpServletRequest request )
+ {
+ super( request );
+
+ dateFormats = new SimpleDateFormat[] {
+ new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss zzz" ),
+ new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss" ),
+ new SimpleDateFormat( "EEE MMM dd HH:mm:ss yyyy" ) };
+
+ headers = new HashMap();
+
+ Enumeration enHeaders = request.getHeaderNames();
+ while ( enHeaders.hasMoreElements() )
+ {
+ String name = (String) enHeaders.nextElement();
+ String value = request.getHeader( name );
+ headers.put( name, value );
+ }
+ }
+
+ public void setHeader( String name, String value )
+ {
+ headers.put( name, value );
+ }
+
+ public long getDateHeader( String name )
+ {
+ String value = (String) headers.get( name );
+ if ( StringUtils.isEmpty( value ) )
+ {
+ // no value? return -1
+ return -1;
+ }
+
+ // Try most common formats first.
+ for ( int i = 0; i < dateFormats.length; i++ )
+ {
+ try
+ {
+ Date date = (Date) dateFormats[i].parseObject( value );
+ return date.getTime();
+ }
+ catch ( java.lang.Exception e )
+ {
+ /* ignore exception */
+ }
+ }
+
+ // Now check for the odd "GMT" formats (hey, it happens)
+ if ( value.endsWith( " GMT" ) )
+ {
+ value = value.substring( 0, value.length() - 4 );
+
+ for ( int i = 0; i < dateFormats.length; i++ )
+ {
+ try
+ {
+ Date date = (Date) dateFormats[i].parseObject( value );
+ return date.getTime();
+ }
+ catch ( java.lang.Exception e )
+ {
+ /* ignore exception */
+ }
+ }
+ }
+
+ // unrecognized format? return -1
+ return -1;
+ }
+
+ public String getHeader( String name )
+ {
+ return (String) headers.get( name );
+ }
+
+ public Enumeration getHeaderNames()
+ {
+ return new Enumeration()
+ {
+ private Iterator iter = headers.keySet().iterator();
+
+ public boolean hasMoreElements()
+ {
+ return iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ return iter.next();
+ }
+ };
+ }
+
+ public int getIntHeader( String name )
+ {
+ String value = getHeader( name );
+ try
+ {
+ return Integer.parseInt( value );
+ }
+ catch ( NumberFormatException e )
+ {
+ return -1;
+ }
+ }
+
+ public void setPathInfo( String alternatePathInfo )
+ {
+ this.pathInfo = alternatePathInfo;
+ }
+
+ public String getPathInfo()
+ {
+ if ( this.pathInfo != null )
+ {
+ return this.pathInfo;
+ }
+
+ return super.getPathInfo();
+ }
+
+ public String getServletPath()
+ {
+ if ( this.pathInfo != null )
+ {
+ return super.getServletPath() + "/" + this.pathInfo;
+ }
+
+ return super.getServletPath();
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java b/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java
new file mode 100644
index 000000000..4b25e84c8
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java
@@ -0,0 +1,57 @@
+/* ========================================================================== *
+ * Copyright (C) 2004-2006, Pier Fumagalli <http://could.it/> *
+ * All rights reserved. *
+ * ========================================================================== *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may *
+ * not use this file except in compliance with the License. You may obtain a *
+ * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
+ * License for the specific language governing permissions and limitations *
+ * under the License. *
+ * *
+ * ========================================================================== */
+package org.betaversion.webdav;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/**
+ * <p>The {@link DAVServlet} class has been moved to a new package and should
+ * now be referred as {@link it.could.webdav.DAVServlet}.</p>
+ *
+ * <p>This class will be preserved for some time (not so long) to give people
+ * time to update their servlet deployment descriptors.</p>
+ *
+ * @author <a href="http://could.it/">Pier Fumagalli</a>
+ * @deprecated This class has been moved into the <code>it.could.webdav</code>
+ * package. Reconfigure your <code>web.xml</code> deployment
+ * descriptor to use {@link it.could.webdav.DAVServlet}.
+ */
+public class DAVServlet extends it.could.webdav.DAVServlet {
+
+ /**
+ * <p>Create a new {@link DAVServlet} instance.</p>
+ */
+ public DAVServlet() {
+ super();
+ }
+
+ /**
+ * <p>Initialize this {@link DAVServlet} instance reporting to the
+ * {@link ServletContext} log that this class is deprecated.</p>
+ */
+ public void init(ServletConfig config)
+ throws ServletException {
+ final ServletContext context = config.getServletContext();
+ context.log("The class \"" + this.getClass().getName()
+ + "\" is deprecated");
+ context.log("Modify the \"web.xml\" deployment descriptor to use \""
+ + it.could.webdav.DAVServlet.class.getName() + "\"");
+ super.init(config);
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html b/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html
new file mode 100644
index 000000000..cdc5bf1ee
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <title>Could.IT WebDAV Servlet</title>
+ </head>
+ <body>
+ <p>
+ This package is deprecated, but preserved to maintain compatibility
+ with previous versions.
+ </p>
+ <p>
+ Please refer to the documentation in the {@link it.could.webdav} package
+ for the new version description.
+ </p>
+ </body>
+</html>
diff --git a/archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt b/archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt
new file mode 100644
index 000000000..56ab6f59e
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt
@@ -0,0 +1,128 @@
+# This is a comment. I love comments.
+
+# This file controls what Internet media types are sent to the client for
+# given file extension(s). Sending the correct media type to the client
+# is important so they know how to handle the content of the file.
+# Extra types can either be added here or by using an AddType directive
+# in your config files. For more information about Internet media types,
+# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
+# registry is at <http://www.iana.org/assignments/media-types/>.
+
+# MIME type Extensions
+
+application/andrew-inset ez
+application/atom+xml atom
+application/java-archive jar
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/mathml+xml mathml
+application/msword doc
+application/octet-stream bin dms lha lzh exe class so dll dmg
+application/oda oda
+application/ogg ogg
+application/pdf pdf
+application/postscript ai eps ps
+application/rdf+xml rdf
+application/smil smi smil
+application/srgs gram
+application/srgs+xml grxml
+application/vnd.mif mif
+application/vnd.mozilla.xul+xml xul
+application/vnd.ms-excel xls
+application/vnd.ms-powerpoint ppt
+application/vnd.rn-realmedia rm
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/voicexml+xml vxml
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-hdf hdf
+application/x-java-jnlp-file jnlp
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/xhtml+xml xhtml xht
+application/xml xml xsl
+application/xml-dtd dtd
+application/xslt+xml xslt
+application/zip zip
+audio/basic au snd
+audio/midi mid midi kar
+audio/mpeg mpga mp2 mp3
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+image/gif gif
+image/ief ief
+image/jp2 jp2
+image/jpeg jpeg jpg jpe
+image/pict pict pic pct
+image/png png
+image/svg+xml svg
+image/tiff tiff tif
+image/vnd.djvu djvu djv
+image/vnd.wap.wbmp wbmp
+image/x-cmu-raster ras
+image/x-icon ico
+image/x-macpaint pntg pnt mac
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-quicktime qtif qti
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+model/iges igs iges
+model/mesh msh mesh silo
+model/vrml wrl vrml
+text/calendar ics ifb
+text/css css
+text/html html htm
+text/plain asc txt
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+video/mp4 mp4
+video/mpeg mpeg mpg mpe
+video/quicktime qt mov
+video/vnd.mpegurl mxu m4u
+video/x-dv dv dif
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
diff --git a/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types b/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types
new file mode 100644
index 000000000..5baed56f7
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types
@@ -0,0 +1,127 @@
+# This is a comment. I love comments.
+
+# This file controls what Internet media types are sent to the client for
+# given file extension(s). Sending the correct media type to the client
+# is important so they know how to handle the content of the file.
+# Extra types can either be added here or by using an AddType directive
+# in your config files. For more information about Internet media types,
+# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
+# registry is at <http://www.iana.org/assignments/media-types/>.
+
+# MIME type Extensions
+
+application/andrew-inset ez
+application/atom+xml atom
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/mathml+xml mathml
+application/msword doc
+application/octet-stream bin dms lha lzh exe class so dll dmg
+application/oda oda
+application/ogg ogg
+application/pdf pdf
+application/postscript ai eps ps
+application/rdf+xml rdf
+application/smil smi smil
+application/srgs gram
+application/srgs+xml grxml
+application/vnd.mif mif
+application/vnd.mozilla.xul+xml xul
+application/vnd.ms-excel xls
+application/vnd.ms-powerpoint ppt
+application/vnd.rn-realmedia rm
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/voicexml+xml vxml
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-hdf hdf
+application/x-java-jnlp-file jnlp
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/xhtml+xml xhtml xht
+application/xml xml xsl
+application/xml-dtd dtd
+application/xslt+xml xslt
+application/zip zip
+audio/basic au snd
+audio/midi mid midi kar
+audio/mpeg mpga mp2 mp3
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+image/gif gif
+image/ief ief
+image/jp2 jp2
+image/jpeg jpeg jpg jpe
+image/pict pict pic pct
+image/png png
+image/svg+xml svg
+image/tiff tiff tif
+image/vnd.djvu djvu djv
+image/vnd.wap.wbmp wbmp
+image/x-cmu-raster ras
+image/x-icon ico
+image/x-macpaint pntg pnt mac
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-quicktime qtif qti
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+model/iges igs iges
+model/mesh msh mesh silo
+model/vrml wrl vrml
+text/calendar ics ifb
+text/css css
+text/html html htm
+text/plain asc txt
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+video/mp4 mp4
+video/mpeg mpeg mpg mpe
+video/quicktime qt mov
+video/vnd.mpegurl mxu m4u
+video/x-dv dv dif
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
diff --git a/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props b/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props
new file mode 100644
index 000000000..815c14baf
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props
@@ -0,0 +1,13 @@
+#
+# A simple property file defining some strings that will be returned and/or
+# used by the Could.IT DAVServlet at different stages of processing
+#
+
+# Returned by DAVServlet in the "getServletInfo()" method
+servlet.information = Could.IT WebDAV Servlet
+
+# Added to the "Server" header every time a request is processed
+servlet.signature = CouldIT-WebDAV
+
+# Version used in build files and combined to information and signature
+version = 0.5-dev
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java
new file mode 100644
index 000000000..f214139db
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java
@@ -0,0 +1,495 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav;
+
+import org.apache.commons.lang.NotImplementedException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * TestableHttpServletRequest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: TestableHttpServletRequest.java 6940 2007-10-16 01:02:02Z joakime $
+ */
+public class TestableHttpServletRequest
+ implements HttpServletRequest
+{
+
+ public TestableHttpServletRequest()
+ {
+ setDefaults();
+ }
+
+ public void setDefaults()
+ {
+ authType = null;
+ scheme = "http";
+ protocol = "HTTP/1.1";
+ serverName = "localhost";
+ serverPort = 80;
+ remoteHost = "localhost";
+ }
+
+ private String authType;
+
+ private String characterEncoding;
+
+ private int contentLength;
+
+ private String contentType;
+
+ private String contextPath;
+
+ private Locale locale;
+
+ private String method;
+
+ private String pathInfo;
+
+ private String pathTranslated;
+
+ private String protocol;
+
+ private String queryString;
+
+ private String remoteAddr;
+
+ private String remoteHost;
+
+ private String remoteUser;
+
+ private String requestedSessionId;
+
+ private boolean requestedSessionIdFromCookie;
+
+ private boolean requestedSessionIdFromUrl;
+
+ private boolean requestedSessionIdValid;
+
+ private StringBuffer requestURL;
+
+ private String scheme;
+
+ private boolean secure;
+
+ private String serverName;
+
+ private int serverPort;
+
+ private String servletPath;
+
+ public Object getAttribute( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getAttribute(String)" ) );
+ }
+
+ public Enumeration getAttributeNames()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getAttributeNames()" ) );
+ }
+
+ public String getAuthType()
+ {
+ return authType;
+ }
+
+ public String getCharacterEncoding()
+ {
+ return characterEncoding;
+ }
+
+ public int getContentLength()
+ {
+ return contentLength;
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ public String getContextPath()
+ {
+ return contextPath;
+ }
+
+ public Cookie[] getCookies()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getCookies()" ) );
+ }
+
+ public long getDateHeader( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getDateHeader(String)" ) );
+ }
+
+ public String getHeader( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getHeader(String)" ) );
+ }
+
+ private Map headers = new HashMap();
+
+ public Enumeration getHeaderNames()
+ {
+ return new IterEnumeration( headers.keySet().iterator() );
+ }
+
+ public Enumeration getHeaders( String name )
+ {
+ throw new NotImplementedException( notImplemented( ".getHeaders(String)" ) );
+ }
+
+ public ServletInputStream getInputStream()
+ throws IOException
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getInputStream()" ) );
+ }
+
+ public int getIntHeader( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getIntHeader(String)" ) );
+ }
+
+ public Locale getLocale()
+ {
+ return locale;
+ }
+
+ public Enumeration getLocales()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getLocales()" ) );
+ }
+
+ public String getMethod()
+ {
+ return method;
+ }
+
+ public String getParameter( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getParameter(String)" ) );
+ }
+
+ public Map getParameterMap()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getParameterMap()" ) );
+ }
+
+ public Enumeration getParameterNames()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getParameterNames()" ) );
+ }
+
+ public String[] getParameterValues( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getParameterValues(String)" ) );
+ }
+
+ public String getPathInfo()
+ {
+ return pathInfo;
+ }
+
+ public String getPathTranslated()
+ {
+ return pathTranslated;
+ }
+
+ public String getProtocol()
+ {
+ return protocol;
+ }
+
+ public String getQueryString()
+ {
+ return queryString;
+ }
+
+ public BufferedReader getReader()
+ throws IOException
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getReader()" ) );
+ }
+
+ public String getRealPath( String path )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getRealPath(String)" ) );
+ }
+
+ public String getRemoteAddr()
+ {
+ return remoteAddr;
+ }
+
+ public String getRemoteHost()
+ {
+ return remoteHost;
+ }
+
+ public String getRemoteUser()
+ {
+ return remoteUser;
+ }
+
+ public RequestDispatcher getRequestDispatcher( String path )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getRequestDispatcher(String)" ) );
+ }
+
+ public String getRequestedSessionId()
+ {
+ return requestedSessionId;
+ }
+
+ public String getRequestURI()
+ {
+ return requestURL.toString();
+ }
+
+ public StringBuffer getRequestURL()
+ {
+ return requestURL;
+ }
+
+ public String getScheme()
+ {
+ return scheme;
+ }
+
+ public String getServerName()
+ {
+ return serverName;
+ }
+
+ public int getServerPort()
+ {
+ return serverPort;
+ }
+
+ public String getServletPath()
+ {
+ return servletPath;
+ }
+
+ public HttpSession getSession()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getSession()" ) );
+ }
+
+ public HttpSession getSession( boolean create )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getSession(boolean)" ) );
+ }
+
+ public Principal getUserPrincipal()
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".getUserPrincipal()" ) );
+ }
+
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ return requestedSessionIdFromCookie;
+ }
+
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ return requestedSessionIdFromUrl;
+ }
+
+ public boolean isRequestedSessionIdFromURL()
+ {
+ return requestedSessionIdFromUrl;
+ }
+
+ public boolean isRequestedSessionIdValid()
+ {
+ return requestedSessionIdValid;
+ }
+
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ public boolean isUserInRole( String role )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".isUserInRole(String)" ) );
+ }
+
+ public void removeAttribute( String name )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".removeAttribute(String)" ) );
+ }
+
+ public void setAttribute( String name, Object o )
+ {
+ // TODO: Implement if needed.
+ throw new NotImplementedException( notImplemented( ".setAttribute(String, Object)" ) );
+ }
+
+ public void setCharacterEncoding( String encoding )
+ throws UnsupportedEncodingException
+ {
+ this.characterEncoding = encoding;
+ }
+
+ public void setContentLength( int contentLength )
+ {
+ this.contentLength = contentLength;
+ }
+
+ public void setContentType( String contentType )
+ {
+ this.contentType = contentType;
+ }
+
+ public void setContextPath( String contextPath )
+ {
+ this.contextPath = contextPath;
+ }
+
+ public void setMethod( String method )
+ {
+ this.method = method;
+ }
+
+ public void setPathInfo( String pathInfo )
+ {
+ this.pathInfo = pathInfo;
+ }
+
+ public void setProtocol( String protocol )
+ {
+ this.protocol = protocol;
+ }
+
+ public void setQueryString( String queryString )
+ {
+ this.queryString = queryString;
+ }
+
+ public void setScheme( String scheme )
+ {
+ this.scheme = scheme;
+ }
+
+ public void setSecure( boolean secure )
+ {
+ this.secure = secure;
+ }
+
+ public void setServerName( String serverName )
+ {
+ this.serverName = serverName;
+ }
+
+ public void setServerPort( int serverPort )
+ {
+ this.serverPort = serverPort;
+ }
+
+ public void setServletPath( String servletPath )
+ {
+ this.servletPath = servletPath;
+ }
+
+ public void setUrl( String urlString )
+ throws MalformedURLException
+ {
+ URL url = new URL( urlString );
+ this.queryString = url.getQuery();
+ this.scheme = url.getProtocol();
+ this.serverName = url.getHost();
+ this.serverPort = url.getPort();
+
+ String path = url.getPath();
+ if ( !path.startsWith( this.servletPath ) )
+ {
+ throw new MalformedURLException( "Unable to operate on request path [" + path
+ + "] outside of servletPath [" + this.servletPath + "]." );
+ }
+
+ this.pathInfo = path.substring( this.servletPath.length() );
+ this.requestURL = new StringBuffer( this.pathInfo );
+ }
+
+ private String notImplemented( String msg )
+ {
+ return msg + " is not implemented in " + this.getClass().getName();
+ }
+
+ class IterEnumeration
+ implements Enumeration
+ {
+ private Iterator iter;
+
+ public IterEnumeration( Iterator it )
+ {
+ this.iter = it;
+ }
+
+ public boolean hasMoreElements()
+ {
+ return this.iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ return this.iter.next();
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java
new file mode 100644
index 000000000..62719cfdb
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.servlet.multiplexed;
+
+import junit.framework.TestCase;
+import org.apache.maven.archiva.webdav.TestableHttpServletRequest;
+import org.apache.maven.archiva.webdav.util.WrappedRepositoryRequest;
+
+import java.net.MalformedURLException;
+
+/**
+ * MultiplexedDavServerRequestTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: MultiplexedDavServerRequestTest.java 6940 2007-10-16 01:02:02Z joakime $
+ */
+public class MultiplexedDavServerRequestTest
+ extends TestCase
+{
+ private void assertMultiURL( String expectedPrefix, String expectedLogicalResource, String url )
+ throws MalformedURLException
+ {
+ TestableHttpServletRequest testrequest = new TestableHttpServletRequest();
+ testrequest.setMethod( "GET" );
+ testrequest.setServletPath( "/repository" );
+ testrequest.setUrl( url );
+
+ WrappedRepositoryRequest wraprequest = new WrappedRepositoryRequest( testrequest );
+ MultiplexedDavServerRequest multirequest = new MultiplexedDavServerRequest( wraprequest );
+
+ assertEquals( expectedPrefix, multirequest.getPrefix() );
+ assertEquals( expectedLogicalResource, multirequest.getLogicalResource() );
+ }
+
+ public void testNormalUsage()
+ throws MalformedURLException
+ {
+ assertMultiURL( "corporate", "/", "http://localhost:9091/repository/corporate" );
+ assertMultiURL( "corporate", "/dom4j/dom4j/1.4", "http://localhost:9091/repository/corporate/dom4j/dom4j/1.4" );
+ }
+
+ public void testHacker()
+ throws MalformedURLException
+ {
+ assertMultiURL( "corporate", "/etc/passwd", "http://localhost:9091/repository/corporate//etc/passwd" );
+ // Since the double ".." puts the path outside of the /corporate/, it will return "/" as a hack fallback.
+ assertMultiURL( "corporate", "/", "http://localhost:9091/repository/corporate/dom4j/../../etc/passwd" );
+ assertMultiURL( "corporate", "/", "http://localhost:9091/repository/corporate/../.." );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java
new file mode 100644
index 000000000..801c59bdf
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import org.apache.maven.archiva.webdav.test.AbstractBasicWebdavProviderTestCase;
+
+/**
+ * SimpleDavServerComponentBasicTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: SimpleDavServerComponentBasicTest.java 5408 2007-01-12 19:42:37Z joakime $
+ */
+public class SimpleDavServerComponentBasicTest
+ extends AbstractBasicWebdavProviderTestCase
+{
+ public SimpleDavServerComponentBasicTest()
+ {
+ super();
+ setProviderHint( "simple" );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java
new file mode 100644
index 000000000..c51b05a5b
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import org.apache.maven.archiva.webdav.test.AbstractWebdavIndexHtmlTestCase;
+
+/**
+ * SimpleDavServerComponentIndexHtmlTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: SimpleDavServerComponentIndexHtmlTest.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public class SimpleDavServerComponentIndexHtmlTest
+ extends AbstractWebdavIndexHtmlTestCase
+{
+ public SimpleDavServerComponentIndexHtmlTest()
+ {
+ super();
+ setProviderHint( "simple" );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java
new file mode 100644
index 000000000..bb17ca8ef
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import org.apache.maven.archiva.webdav.test.AbstractMultiWebdavProviderTestCase;
+
+/**
+ * SimpleDavServerComponentCrossTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: SimpleDavServerComponentMultiTest.java 5408 2007-01-12 19:42:37Z joakime $
+ */
+public class SimpleDavServerComponentMultiTest
+ extends AbstractMultiWebdavProviderTestCase
+{
+ public SimpleDavServerComponentMultiTest()
+ {
+ super();
+ setProviderHint( "simple" );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java
new file mode 100644
index 000000000..7cec7d7af
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.simple;
+
+import org.apache.maven.archiva.webdav.test.AbstractWebdavServer;
+
+/**
+ * SimpleWebdavServer
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: SimpleWebdavServer.java 5379 2007-01-07 22:54:41Z joakime $
+ */
+public class SimpleWebdavServer
+ extends AbstractWebdavServer
+{
+ public static void main( String[] args )
+ {
+ try
+ {
+ SimpleWebdavServer server = new SimpleWebdavServer();
+ server.init();
+ server.startServer();
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ protected String getProviderHint()
+ {
+ return "simple";
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java
new file mode 100644
index 000000000..82eab6cf8
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.commons.httpclient.HttpURL;
+import org.apache.maven.archiva.webdav.servlet.basic.BasicWebDavServlet;
+import org.apache.webdav.lib.WebdavResource;
+import org.codehaus.plexus.util.IOUtil;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.ServletHandler;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * AbstractBasicWebdavProviderTestCase
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractBasicWebdavProviderTestCase.java 6000 2007-03-04 22:01:49Z joakime $
+ */
+public abstract class AbstractBasicWebdavProviderTestCase
+ extends AbstractWebdavProviderTestCase
+{
+ private File serverRepoDir;
+
+ private WebdavResource davRepo;
+
+ /** The Jetty Server. */
+ private Server server;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ // Initialize server contents directory.
+
+ serverRepoDir = getTestDir( "sandbox" );
+
+ // Setup the Jetty Server.
+
+ System.setProperty( "DEBUG", "" );
+ System.setProperty( "org.mortbay.log.class", "org.slf4j.impl.SimpleLogger" );
+
+ server = new Server( PORT );
+ WebAppContext webAppConfig = new WebAppContext( server, getTestFile( "src/test/webapp" ).getCanonicalPath(), "/" );
+
+ ServletHandler servletHandler = webAppConfig.getServletHandler();
+
+ ServletHolder holder = servletHandler.addServletWithMapping( BasicWebDavServlet.class, CONTEXT + "/*" );
+
+ holder.setInitParameter( "dav.root", serverRepoDir.getAbsolutePath() );
+
+ server.start();
+
+ // Setup Client Side
+
+ HttpURL httpSandboxUrl = new HttpURL( "http://localhost:" + PORT + CONTEXT + "/" );
+
+ try
+ {
+ davRepo = new WebdavResource( httpSandboxUrl );
+
+ davRepo.setDebug( 8 );
+
+ davRepo.setPath( CONTEXT );
+ }
+ catch ( IOException e )
+ {
+ tearDown();
+ throw e;
+ }
+ }
+
+ protected void tearDown()
+ throws Exception
+ {
+ serverRepoDir = null;
+
+ if ( server != null )
+ {
+ try
+ {
+ server.stop();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+ server = null;
+ }
+
+ if ( davRepo != null )
+ {
+ try
+ {
+ davRepo.close();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+
+ davRepo = null;
+ }
+
+ super.tearDown();
+ }
+
+ // --------------------------------------------------------------------
+ // Actual Test Cases.
+ // --------------------------------------------------------------------
+
+ public void testPutGet()
+ throws Exception
+ {
+ // Quote: Rocky
+ String contents = "yo!\n";
+
+ assertDavTouchFile( davRepo, CONTEXT, "data.txt", contents );
+
+ InputStream inputStream = davRepo.getMethodData( CONTEXT + "/data.txt" );
+
+ assertEquals( contents, IOUtil.toString( inputStream ) );
+ }
+
+ public void testCollectionTasks()
+ throws Exception
+ {
+ // Create a few collections.
+ assertDavMkDir( davRepo, CONTEXT + "/bar" );
+ assertDavMkDir( davRepo, CONTEXT + "/bar/foo" );
+
+ // Remove a collection
+ davRepo.setPath( CONTEXT );
+ if ( !davRepo.deleteMethod( CONTEXT + "/bar/foo" ) )
+ {
+ fail( "Unable to remove <" + CONTEXT + "/bar/foo> on <" + davRepo.getHttpURL().toString() + "> due to <"
+ + davRepo.getStatusMessage() + ">" );
+ }
+
+ assertDavDirNotExists( davRepo, CONTEXT + "/bar/foo" );
+ }
+
+ public void testResourceCopy()
+ throws Exception
+ {
+ // Lyrics: Cool and the Gang - Celebrate Good Times
+ String contents = "we're gonna have a good time tonite. lets celebrate. it's a celebration. "
+ + "cel-e-brate good times, come on!";
+
+ // Create a few collections.
+ assertDavMkDir( davRepo, CONTEXT + "/bar" );
+ assertDavMkDir( davRepo, CONTEXT + "/foo" );
+
+ // Create a resource
+ assertDavTouchFile( davRepo, CONTEXT + "/bar", "data.txt", contents );
+
+ // Test for existance of resource
+ assertDavFileExists( davRepo, CONTEXT + "/bar", "data.txt" );
+ assertDavFileNotExists( davRepo, CONTEXT + "/foo", "data.txt" );
+
+ // Copy resource
+ String source = CONTEXT + "/bar/data.txt";
+ String dest = CONTEXT + "/foo/data.txt";
+ if ( !davRepo.copyMethod( source, dest ) )
+ {
+ fail( "Unable to copy <" + source + "> to <" + dest + "> on <" + davRepo.getHttpURL().toString()
+ + "> due to <" + davRepo.getStatusMessage() + ">" );
+ }
+
+ // Test for existance of resource
+ assertDavFileExists( davRepo, CONTEXT + "/bar", "data.txt" );
+ assertDavFileExists( davRepo, CONTEXT + "/foo", "data.txt" );
+ }
+
+ public void testResourceMove()
+ throws Exception
+ {
+ // Lyrics: Men At Work - Who Can It Be Now
+ String contents = "Who can it be knocking at my door?\n" + "Make no sound, tip-toe across the floor.\n"
+ + "If he hears, he'll knock all day,\n" + "I'll be trapped, and here I'll have to stay.\n"
+ + "I've done no harm, I keep to myself;\n" + "There's nothing wrong with my state of mental health.\n"
+ + "I like it here with my childhood friend;\n" + "Here they come, those feelings again!\n";
+
+ // Create a few collections.
+ assertDavMkDir( davRepo, CONTEXT + "/bar" );
+ assertDavMkDir( davRepo, CONTEXT + "/foo" );
+
+ // Create a resource
+ assertDavTouchFile( davRepo, CONTEXT + "/bar", "data.txt", contents );
+
+ // Test for existance of resource
+ assertDavFileExists( davRepo, CONTEXT + "/bar", "data.txt" );
+ assertDavFileNotExists( davRepo, CONTEXT + "/foo", "data.txt" );
+
+ // Copy resource
+ String source = CONTEXT + "/bar/data.txt";
+ String dest = CONTEXT + "/foo/data.txt";
+ if ( !davRepo.moveMethod( source, dest ) )
+ {
+ fail( "Unable to move <" + source + "> to <" + dest + "> on <" + davRepo.getHttpURL().toString()
+ + "> due to <" + davRepo.getStatusMessage() + ">" );
+ }
+
+ // Test for existance of resource
+ assertDavFileNotExists( davRepo, CONTEXT + "/bar", "data.txt" );
+ assertDavFileExists( davRepo, CONTEXT + "/foo", "data.txt" );
+ }
+
+ public void testResourceDelete()
+ throws Exception
+ {
+ // Lyrics: Men At Work - Down Under
+ String contents = "Lying in a den in Bombay\n" + "With a slack jaw, and not much to say\n"
+ + "I said to the man, \"Are you trying to tempt me\"\n" + "Because I come from the land of plenty?\n";
+
+ // Create a few collections.
+ assertDavMkDir( davRepo, CONTEXT + "/bar" );
+
+ // Create a resource
+ assertDavTouchFile( davRepo, CONTEXT + "/bar", "data.txt", contents );
+
+ // Move resource
+ davRepo.setPath( CONTEXT );
+ if ( !davRepo.deleteMethod( CONTEXT + "/bar/data.txt" ) )
+ {
+ fail( "Unable to remove <" + CONTEXT + "/bar/data.txt> on <" + davRepo.getHttpURL().toString()
+ + "> due to <" + davRepo.getStatusMessage() + ">" );
+ }
+
+ // Test for existance via webdav interface.
+ assertDavFileNotExists( davRepo, CONTEXT + "/bar", "data.txt" );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java
new file mode 100644
index 000000000..71d2d46c0
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.HttpURL;
+import org.apache.webdav.lib.WebdavResource;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.ServletHandler;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * AbstractMultiWebdavProviderTestCase
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractMultiWebdavProviderTestCase.java 5997 2007-03-04 19:41:15Z joakime $
+ */
+public abstract class AbstractMultiWebdavProviderTestCase
+ extends AbstractWebdavProviderTestCase
+{
+ File serverSandboxDir;
+
+ File serverSnapshotsDir;
+
+ /** The Jetty Server. */
+ private Server server;
+
+ private WebdavResource davSnapshots;
+
+ private WebdavResource davSandbox;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ // Initialize server contents directory.
+
+ serverSandboxDir = getTestDir( "sandbox" );
+ serverSnapshotsDir = getTestDir( "snapshots" );
+
+ // Setup the Jetty Server.
+
+ System.setProperty( "DEBUG", "" );
+ System.setProperty( "org.mortbay.log.class", "org.slf4j.impl.SimpleLogger" );
+
+ server = new Server( PORT );
+
+ WebAppContext webAppConfig = new WebAppContext( server, getTestFile( "src/test/webapp" ).getCanonicalPath(), "/" );
+ ServletHandler servletHandler = webAppConfig.getServletHandler();
+
+ ServletHolder holder = servletHandler.addServletWithMapping( TestMultiWebDavServlet.class, CONTEXT + "/*" );
+ holder.setInitParameter( "root.sandbox", serverSandboxDir.getAbsolutePath() );
+ holder.setInitParameter( "root.snapshots", serverSnapshotsDir.getAbsolutePath() );
+
+ System.out.println( "root.sandbox = " + serverSandboxDir.getAbsolutePath() );
+ System.out.println( "root.snapshots = " + serverSnapshotsDir.getAbsolutePath() );
+
+ server.start();
+
+ // Setup Client Side
+
+ HttpURL httpSandboxUrl = new HttpURL( "http://localhost:" + PORT + CONTEXT + "/sandbox/" );
+ HttpURL httpSnapshotsUrl = new HttpURL( "http://localhost:" + PORT + CONTEXT + "/snapshots/" );
+
+ try
+ {
+ davSandbox = new WebdavResource( httpSandboxUrl );
+ davSnapshots = new WebdavResource( httpSnapshotsUrl );
+
+ davSandbox.setDebug( 8 );
+ davSnapshots.setDebug( 8 );
+
+ davSandbox.setPath( CONTEXT + "/sandbox/" );
+ davSnapshots.setPath( CONTEXT + "/snapshots/" );
+ }
+ catch ( IOException e )
+ {
+ tearDown();
+ throw e;
+ }
+ }
+
+ protected void tearDown()
+ throws Exception
+ {
+ serverRootDir = null;
+
+ if ( server != null )
+ {
+ try
+ {
+ server.stop();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+ server = null;
+ }
+
+ if ( davSandbox != null )
+ {
+ try
+ {
+ davSandbox.close();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+
+ davSandbox = null;
+ }
+
+ if ( davSnapshots != null )
+ {
+ try
+ {
+ davSnapshots.close();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+
+ davSnapshots = null;
+ }
+
+ super.tearDown();
+ }
+
+ public void testResourceMoveCrossWebdav()
+ throws Exception
+ {
+ // Create a few collections.
+ assertDavMkDir( davSandbox, CONTEXT + "/sandbox/bar" );
+ assertDavMkDir( davSnapshots, CONTEXT + "/snapshots/foo" );
+
+ // Create a resource
+ assertDavTouchFile( davSandbox, CONTEXT + "/sandbox/bar", "data.txt", "yo!" );
+
+ // Move resource URL to URL (Across the WebDav Servlets)
+ davSandbox.setPath( CONTEXT + "/sandbox/bar" );
+ String source = CONTEXT + "/sandbox/bar/data.txt";
+ String dest = "http://localhost:" + PORT + CONTEXT + "/snapshots/foo/data.txt";
+ if ( !davSandbox.moveMethod( source, dest ) )
+ {
+ // TODO: remove when fully implemented.
+ if ( davSandbox.getStatusCode() == HttpStatus.SC_NOT_IMPLEMENTED )
+ {
+ // return quietly, as the server reported no support for this method.
+ return;
+ }
+
+ fail( "Unable to move <" + source + "> to <" + dest + "> on <" + davSandbox.getHttpURL().toString()
+ + "> due to <" + davSandbox.getStatusMessage() + ">" );
+ }
+
+ assertDavFileNotExists( davSandbox, CONTEXT + "/sandbox/bar", "data.txt" );
+ assertDavFileExists( davSnapshots, CONTEXT + "/snapshots/foo", "data.txt" );
+ }
+
+ public void testResourceDoesNotExist()
+ throws Exception
+ {
+ // Create a few collections.
+ assertDavMkDir( davSandbox, CONTEXT + "/sandbox/bar" );
+ assertDavMkDir( davSnapshots, CONTEXT + "/snapshots/foo" );
+
+ // Create a resource
+ assertDavTouchFile( davSandbox, CONTEXT + "/sandbox/bar", "data.txt", "yo!" );
+
+ // Get bad resources URLs
+ String urlPrefix = "http://localhost:" + PORT + CONTEXT;
+ assertGet404( urlPrefix + "/sandbox/a/resource/that/does/not/exist.html" );
+ assertGet404( urlPrefix + "/" );
+ assertGet404( urlPrefix + "/snapshots/foo/index.html" );
+ assertGet404( urlPrefix + "/sandbox/bar.html" );
+ assertGet404( urlPrefix + "/nonexistant/index.html" );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java
new file mode 100644
index 000000000..f01790430
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.commons.httpclient.HttpURL;
+import org.apache.maven.archiva.webdav.servlet.basic.BasicWebDavServlet;
+import org.apache.webdav.lib.WebdavResource;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.ServletHandler;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+import java.io.File;
+import java.io.IOException;
+
+public abstract class AbstractWebdavIndexHtmlTestCase
+ extends AbstractWebdavProviderTestCase
+{
+ private File serverRepoDir;
+
+ private WebdavResource davRepo;
+
+ /** The Jetty Server. */
+ private Server server;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ // Initialize server contents directory.
+
+ serverRepoDir = getTestDir( "sandbox" );
+
+ // Setup the Jetty Server.
+
+ System.setProperty( "DEBUG", "" );
+ System.setProperty( "org.mortbay.log.class", "org.slf4j.impl.SimpleLogger" );
+
+ server = new Server( PORT );
+ WebAppContext webAppConfig = new WebAppContext( server, getTestFile( "src/test/webapp" ).getCanonicalPath(), "/" );
+
+ ServletHandler servletHandler = webAppConfig.getServletHandler();
+
+ ServletHolder holder = servletHandler.addServletWithMapping( BasicWebDavServlet.class, CONTEXT + "/*" );
+
+ holder.setInitParameter( BasicWebDavServlet.INIT_ROOT_DIRECTORY, serverRepoDir.getAbsolutePath() );
+ holder.setInitParameter( BasicWebDavServlet.INIT_USE_INDEX_HTML, "true" );
+
+ server.start();
+
+ // Setup Client Side
+
+ HttpURL httpSandboxUrl = new HttpURL( "http://localhost:" + PORT + CONTEXT + "/" );
+
+ try
+ {
+ davRepo = new WebdavResource( httpSandboxUrl );
+
+ davRepo.setDebug( 8 );
+
+ davRepo.setPath( CONTEXT );
+ }
+ catch ( IOException e )
+ {
+ tearDown();
+ throw e;
+ }
+ }
+
+ protected void tearDown()
+ throws Exception
+ {
+ serverRepoDir = null;
+
+ if ( server != null )
+ {
+ try
+ {
+ server.stop();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+ server = null;
+ }
+
+ if ( davRepo != null )
+ {
+ try
+ {
+ davRepo.close();
+ }
+ catch ( Exception e )
+ {
+ /* ignore */
+ }
+
+ davRepo = null;
+ }
+
+ super.tearDown();
+ }
+
+ public void testCollectionIndexHtml()
+ throws Exception
+ {
+ // Lyrics: Colin Hay - Overkill
+ String contents = "I cant get to sleep\n" + "I think about the implications\n" + "Of diving in too deep\n"
+ + "And possibly the complications\n" + "Especially at night\n" + "I worry over situations\n"
+ + "I know will be alright\n" + "Perahaps its just my imagination\n" + "Day after day it reappears\n"
+ + "Night after night my heartbeat, shows the fear\n" + "Ghosts appear and fade away";
+
+ // Create a few collections.
+ assertDavMkDir( davRepo, CONTEXT + "/bar" );
+ assertDavMkDir( davRepo, CONTEXT + "/foo" );
+
+ // Create a resource
+ assertDavTouchFile( davRepo, CONTEXT + "/bar", "index.html", contents );
+
+ // Test for existance of resource
+ assertDavFileExists( davRepo, CONTEXT + "/bar", "index.html" );
+ assertDavFileNotExists( davRepo, CONTEXT + "/foo", "index.html" );
+
+ // Copy resource
+ String actual = davRepo.getMethodDataAsString( CONTEXT + "/bar/" );
+
+ assertEquals( contents, actual );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java
new file mode 100644
index 000000000..33a210a8c
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.archiva.webdav.DavServerManager;
+import org.apache.webdav.lib.WebdavResource;
+import org.apache.webdav.lib.WebdavResources;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * AbstractWebdavProviderTestCase
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractWebdavProviderTestCase.java 5997 2007-03-04 19:41:15Z joakime $
+ */
+public abstract class AbstractWebdavProviderTestCase
+ extends PlexusInSpringTestCase
+{
+ public static final int PORT = 4321;
+
+ public static final String CONTEXT = "/repos";
+
+ protected File serverRootDir = null;
+
+ private DavServerManager manager;
+
+ private String providerHint = "simple";
+
+ public DavServerManager getManager()
+ {
+ return manager;
+ }
+
+ public String getProviderHint()
+ {
+ return providerHint;
+ }
+
+ public void setManager( DavServerManager manager )
+ {
+ this.manager = manager;
+ }
+
+ public void setProviderHint( String providerHint )
+ {
+ this.providerHint = providerHint;
+ }
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+ try
+ {
+ manager = (DavServerManager) lookup( DavServerManager.ROLE, getProviderHint() );
+ serverRootDir = getRootDir();
+ }
+ catch ( Exception e )
+ {
+ tearDown();
+ throw e;
+ }
+ }
+
+ protected void tearDown()
+ throws Exception
+ {
+ serverRootDir = null;
+
+ super.tearDown();
+ }
+
+ protected void dumpCollection( WebdavResource webdavResource, String path )
+ throws Exception
+ {
+ webdavResource.setPath( path );
+ WebdavResource resources[] = webdavResource.listWebdavResources();
+
+ System.out.println( "Dump Collection [" + path + "]: " + resources.length + " hits." );
+
+ dumpCollectionRecursive( "", webdavResource, path );
+ }
+
+ protected void dumpCollectionRecursive( String indent, WebdavResource webdavResource, String path )
+ throws Exception
+ {
+ if ( indent.length() > 12 )
+ {
+ return;
+ }
+
+ WebdavResource resources[] = webdavResource.listWebdavResources();
+
+ for ( int i = 0; i < resources.length; i++ )
+ {
+ System.out.println( indent + "WebDavResource[" + path + "|" + i + "]: "
+ + ( resources[i].isCollection() ? "(collection) " : "" ) + resources[i].getName() );
+
+ if ( resources[i].isCollection() )
+ {
+ dumpCollectionRecursive( indent + " ", resources[i], path + "/" + resources[i].getName() );
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+ // Actual Test Cases.
+ // --------------------------------------------------------------------
+
+ public void assertNotExists( File basedir, String relativePath )
+ {
+ assertNotExists( new File( basedir, relativePath ) );
+ }
+
+ public void assertNotExists( File file )
+ {
+ if ( file.exists() )
+ {
+ fail( "Unexpected path <" + file.getAbsolutePath() + "> should not exist." );
+ }
+ }
+
+ public void assertExists( File basedir, String relativePath )
+ {
+ assertExists( new File( basedir, relativePath ) );
+ }
+
+ public void assertExists( File file )
+ {
+ if ( !file.exists() )
+ {
+ fail( "Expected path <" + file.getAbsolutePath() + "> does not exist." );
+ }
+ }
+
+ private void resetDirectory( File dir )
+ {
+ try
+ {
+ FileUtils.deleteDirectory( dir );
+ }
+ catch ( IOException e )
+ {
+ fail( "Unable to delete test directory [" + dir.getAbsolutePath() + "]." );
+ }
+
+ if ( dir.exists() )
+ {
+ fail( "Unable to execute test, test directory [" + dir.getAbsolutePath()
+ + "] exists, and cannot be deleted by the test case." );
+ }
+
+ if ( !dir.mkdirs() )
+ {
+ fail( "Unable to execute test, test directory [" + dir.getAbsolutePath() + "] cannot be created." );
+ }
+ }
+
+ private File getRootDir()
+ {
+ if ( this.serverRootDir == null )
+ {
+ String clazz = this.getClass().getName();
+ clazz = clazz.substring( clazz.lastIndexOf( "." ) + 1 );
+ serverRootDir = new File( "target/test-contents-" + clazz + "/" + getName() );
+
+ resetDirectory( serverRootDir );
+ }
+
+ return serverRootDir;
+ }
+
+ protected File getTestDir( String subdir )
+ {
+ File testDir = new File( getRootDir(), subdir );
+ resetDirectory( testDir );
+ return testDir;
+ }
+
+ public boolean isHttpStatusOk( WebdavResource webdavResource )
+ {
+ int statusCode = webdavResource.getStatusCode();
+
+ if ( statusCode == HttpStatus.SC_MULTI_STATUS )
+ {
+ // TODO: find out multi-status values.
+ }
+
+ return ( statusCode >= 200 ) && ( statusCode < 300 );
+ }
+
+ public void assertDavMkDir( WebdavResource webdavResource, String collectionName )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ if ( !webdavResource.mkcolMethod( collectionName ) )
+ {
+ fail( "Unable to create collection/dir <" + collectionName + "> against <" + httpurl + "> due to <"
+ + webdavResource.getStatusMessage() + ">" );
+ }
+
+ assertDavDirExists( webdavResource, collectionName );
+ }
+
+ public void assertDavFileExists( WebdavResource webdavResource, String path, String filename )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ if ( !webdavResource.headMethod( path + "/" + filename ) )
+ {
+ fail( "Unable to verify that file/contents <" + path + "/" + filename + "> exists against <" + httpurl
+ + "> due to <" + webdavResource.getStatusMessage() + ">" );
+ }
+
+ String oldPath = webdavResource.getPath();
+ try
+ {
+ webdavResource.setPath( path );
+
+ WebdavResources resources = webdavResource.getChildResources();
+
+ WebdavResource testResource = resources.getResource( filename );
+
+ if ( testResource == null )
+ {
+ fail( "The file/contents <" + path + "/" + filename + "> does not exist in <" + httpurl + ">" );
+ }
+
+ if ( testResource.isCollection() )
+ {
+ fail( "The file/contents <" + path + "/" + filename
+ + "> is incorrectly being reported as a collection." );
+ }
+ }
+ finally
+ {
+ webdavResource.setPath( oldPath );
+ }
+ }
+
+ public void assertDavFileNotExists( WebdavResource webdavResource, String path, String filename )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ if ( webdavResource.headMethod( path + "/" + filename ) )
+ {
+ fail( "Encountered unexpected file/contents <" + path + "/" + filename + "> at <" + httpurl + ">" );
+ }
+
+ String oldPath = webdavResource.getPath();
+ try
+ {
+ webdavResource.setPath( path );
+
+ WebdavResources resources = webdavResource.getChildResources();
+
+ WebdavResource testResource = resources.getResource( filename );
+
+ if ( testResource == null )
+ {
+ // Nothing found. we're done.
+ return;
+ }
+
+ if ( !testResource.isCollection() )
+ {
+ fail( "Encountered unexpected file/contents <" + path + "/" + filename + "> at <" + httpurl + ">" );
+ }
+ }
+ finally
+ {
+ webdavResource.setPath( oldPath );
+ }
+ }
+
+ public void assertDavDirExists( WebdavResource webdavResource, String path )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ String oldPath = webdavResource.getPath();
+ try
+ {
+ webdavResource.setPath( path );
+
+ if ( !webdavResource.isCollection() )
+ {
+ if ( !isHttpStatusOk( webdavResource ) )
+ {
+ fail( "Unable to verify that path <" + path + "> is really a collection against <" + httpurl
+ + "> due to <" + webdavResource.getStatusMessage() + ">" );
+ }
+ }
+ }
+ finally
+ {
+ webdavResource.setPath( oldPath );
+ }
+ }
+
+ public void assertDavDirNotExists( WebdavResource webdavResource, String path )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ String oldPath = webdavResource.getPath();
+ try
+ {
+ webdavResource.setPath( path );
+
+ if ( webdavResource.isCollection() )
+ {
+ fail( "Encountered unexpected collection <" + path + "> at <" + httpurl + ">" );
+ }
+ }
+ catch ( HttpException e )
+ {
+ if ( e.getReasonCode() == HttpStatus.SC_NOT_FOUND )
+ {
+ // Expected path.
+ return;
+ }
+
+ fail( "Unable to set path due to HttpException: " + e.getReasonCode() + ":" + e.getReason() );
+ }
+ finally
+ {
+ webdavResource.setPath( oldPath );
+ }
+ }
+
+ public void assertDavTouchFile( WebdavResource webdavResource, String path, String filename, String contents )
+ throws Exception
+ {
+ String httpurl = webdavResource.getHttpURL().toString();
+
+ webdavResource.setPath( path );
+
+ if ( !webdavResource.putMethod( path + "/" + filename, contents ) )
+ {
+ fail( "Unable to create file/contents <" + path + "/" + filename + "> against <" + httpurl + "> due to <"
+ + webdavResource.getStatusMessage() + ">" );
+ }
+
+ assertDavFileExists( webdavResource, path, filename );
+ }
+
+ protected void assertGet404( String url )
+ throws IOException
+ {
+ HttpClient client = new HttpClient();
+ GetMethod method = new GetMethod( url );
+
+ try
+ {
+ client.executeMethod( method );
+
+ if ( method.getStatusCode() == 404 )
+ {
+ // Expected path.
+ return;
+ }
+
+ fail( "Request for resource " + url + " should have resulted in an HTTP 404 (Not Found) response, "
+ + "instead got code " + method.getStatusCode() + " <" + method.getStatusText() + ">." );
+ }
+ catch ( HttpException e )
+ {
+ System.err.println( "HTTP Response: " + e.getReasonCode() + " " + e.getReason() );
+ throw e;
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java
new file mode 100644
index 000000000..066659098
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.maven.archiva.webdav.DavServerManager;
+import org.apache.maven.archiva.webdav.servlet.basic.BasicWebDavServlet;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusConstants;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.codehaus.plexus.context.DefaultContext;
+import org.codehaus.plexus.util.FileUtils;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHandler;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AbstractWebdavServer - Baseline server for starting up a BasicWebDavServlet to allow experimentation with.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: AbstractWebdavServer.java 5407 2007-01-12 19:41:09Z joakime $
+ */
+public abstract class AbstractWebdavServer
+{
+ public static final int PORT = 14541;
+
+ protected PlexusContainer container;
+
+ protected String basedir;
+
+ protected Map context;
+
+ /** the jetty server */
+ protected Server server;
+
+ private DavServerManager manager;
+
+ public void init()
+ {
+ context = new HashMap();
+ }
+
+ public String getBasedir()
+ {
+ if ( basedir != null )
+ {
+ return basedir;
+ }
+
+ basedir = System.getProperty( "basedir" );
+ if ( basedir == null )
+ {
+ basedir = new File( "" ).getAbsolutePath();
+ }
+
+ return basedir;
+ }
+
+ public File getTestFile( String path )
+ {
+ return new File( getBasedir(), path );
+ }
+
+ protected abstract String getProviderHint();
+
+ public void startServer()
+ throws Exception
+ {
+ basedir = getBasedir();
+
+ // ----------------------------------------------------------------------------
+ // Context Setup
+ // ----------------------------------------------------------------------------
+
+ context = new HashMap();
+
+ context.put( "basedir", getBasedir() );
+
+ customizeContext( new DefaultContext( context ) );
+
+ boolean hasPlexusHome = context.containsKey( "plexus.home" );
+
+ if ( !hasPlexusHome )
+ {
+ File f = getTestFile( "target/plexus-home" );
+
+ if ( !f.isDirectory() )
+ {
+ f.mkdir();
+ }
+
+ context.put( "plexus.home", f.getAbsolutePath() );
+ }
+
+ // ----------------------------------------------------------------------------
+ // Configuration
+ // ----------------------------------------------------------------------------
+
+ String config = getCustomConfigurationName();
+ InputStream is;
+
+ if ( config != null )
+ {
+ is = getClass().getClassLoader().getResourceAsStream( config );
+
+ if ( is == null )
+ {
+ try
+ {
+ File configFile = new File( config );
+
+ if ( configFile.exists() )
+ {
+ is = new FileInputStream( configFile );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new Exception( "The custom configuration specified is null: " + config );
+ }
+ }
+
+ }
+ else
+ {
+ config = getConfigurationName( null );
+
+ is = getClass().getClassLoader().getResourceAsStream( config );
+ }
+
+ // Look for a configuration associated with this test but return null if we
+ // can't find one so the container doesn't look for a configuration that we
+ // know doesn't exist. Not all tests have an associated Foo.xml for testing.
+
+ if ( is == null )
+ {
+ config = null;
+ }
+ else
+ {
+ is.close();
+ }
+
+ // ----------------------------------------------------------------------------
+ // Create the container
+ // ----------------------------------------------------------------------------
+
+ container = createContainerInstance( context, config );
+
+ // ----------------------------------------------------------------------------
+ // Create the DavServerManager
+ // ----------------------------------------------------------------------------
+
+ manager = (DavServerManager) container.lookup( DavServerManager.ROLE, getProviderHint() );
+
+ // ----------------------------------------------------------------------------
+ // Create the jetty server
+ // ----------------------------------------------------------------------------
+
+ System.setProperty( "DEBUG", "" );
+ System.setProperty( "org.mortbay.log.class", "org.slf4j.impl.SimpleLogger" );
+
+ server = new Server( PORT );
+ Context root = new Context( server, "/", Context.SESSIONS );
+ ServletHandler servletHandler = root.getServletHandler();
+ root.setContextPath( "/" );
+ root.setAttribute( PlexusConstants.PLEXUS_KEY, container );
+
+ // ----------------------------------------------------------------------------
+ // Configure the webdav servlet
+ // ----------------------------------------------------------------------------
+
+ ServletHolder holder = servletHandler.addServletWithMapping( BasicWebDavServlet.class, "/projects/*" );
+
+ // Initialize server contents directory.
+ File serverContentsDir = new File( "target/test-server/" );
+
+ FileUtils.deleteDirectory( serverContentsDir );
+ if ( serverContentsDir.exists() )
+ {
+ throw new IllegalStateException( "Unable to execute test, server contents test directory ["
+ + serverContentsDir.getAbsolutePath() + "] exists, and cannot be deleted by the test case." );
+ }
+
+ if ( !serverContentsDir.mkdirs() )
+ {
+ throw new IllegalStateException( "Unable to execute test, server contents test directory ["
+ + serverContentsDir.getAbsolutePath() + "] cannot be created." );
+ }
+
+ holder.setInitParameter( "dav.root", serverContentsDir.getAbsolutePath() );
+
+ // ----------------------------------------------------------------------------
+ // Start the jetty server
+ // ----------------------------------------------------------------------------
+
+ server.start();
+ }
+
+ protected PlexusContainer createContainerInstance( Map context, String configuration )
+ throws PlexusContainerException
+ {
+ return new DefaultPlexusContainer( "test", context, configuration );
+ }
+
+ protected void customizeContext( DefaultContext ctx )
+ {
+ /* override to specify more */
+ }
+
+ protected String getCustomConfigurationName()
+ {
+ /* override to specify */
+ return null;
+ }
+
+ protected String getConfigurationName( String subname )
+ throws Exception
+ {
+ return getClass().getName().replace( '.', '/' ) + ".xml";
+ }
+
+ public void stopServer()
+ {
+ if ( server != null )
+ {
+ try
+ {
+ server.stop();
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ if ( container != null )
+ {
+ container.dispose();
+ }
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java
new file mode 100644
index 000000000..4a8506eb0
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.test;
+
+import org.apache.maven.archiva.webdav.DavServerException;
+import org.apache.maven.archiva.webdav.servlet.multiplexed.MultiplexedWebDavServlet;
+
+import javax.servlet.ServletConfig;
+import java.io.File;
+
+/**
+ * TestServlet
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: TestMultiWebDavServlet.java 5407 2007-01-12 19:41:09Z joakime $
+ */
+public class TestMultiWebDavServlet
+ extends MultiplexedWebDavServlet
+{
+ public void initServers( ServletConfig config )
+ throws DavServerException
+ {
+ String rootSandbox = config.getInitParameter( "root.sandbox" );
+ String rootSnapshots = config.getInitParameter( "root.snapshots" );
+
+ createServer( "sandbox", new File( rootSandbox ), config );
+ createServer( "snapshots", new File( rootSnapshots ), config );
+ }
+} \ No newline at end of file
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java
new file mode 100644
index 000000000..9bdbf94f6
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.util;
+
+import org.codehaus.plexus.PlexusTestCase;
+
+/**
+ * MimeTypesTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: MimeTypesTest.java 6556 2007-06-20 20:44:46Z joakime $
+ */
+public class MimeTypesTest extends PlexusTestCase
+{
+ public void testGetMimeType() throws Exception
+ {
+ MimeTypes mime = (MimeTypes) lookup( MimeTypes.class );
+ assertNotNull( "MimeTypes should not be null.", mime );
+
+ assertEquals( "application/pdf", mime.getMimeType( "big-book.pdf" ) );
+ assertEquals( "application/octet-stream", mime.getMimeType( "BookMaker.class" ) );
+ assertEquals( "application/vnd.ms-powerpoint", mime.getMimeType( "TypeSetting.ppt" ) );
+ assertEquals( "application/java-archive", mime.getMimeType( "BookViewer.jar" ) );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java
new file mode 100644
index 000000000..a0b665735
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.maven.archiva.webdav.util;
+
+import org.apache.maven.archiva.webdav.TestableHttpServletRequest;
+import org.codehaus.plexus.PlexusTestCase;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.MalformedURLException;
+
+/**
+ * WrappedRepositoryRequestTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id: WrappedRepositoryRequestTest.java 6940 2007-10-16 01:02:02Z joakime $
+ */
+public class WrappedRepositoryRequestTest
+ extends PlexusTestCase
+{
+ private HttpServletRequest createHttpServletGetRequest( String url )
+ throws MalformedURLException
+ {
+ TestableHttpServletRequest testrequest = new TestableHttpServletRequest();
+ testrequest.setMethod( "GET" );
+ testrequest.setServletPath( "/repository" );
+ testrequest.setUrl( url );
+
+ return testrequest;
+ }
+
+ public void testShort()
+ throws Exception
+ {
+ HttpServletRequest request = createHttpServletGetRequest( "http://machine.com/repository/org" );
+ WrappedRepositoryRequest wrapreq = new WrappedRepositoryRequest( request );
+ assertNotNull( wrapreq );
+
+ assertEquals( "/repository", wrapreq.getServletPath() );
+ assertEquals( "/org", wrapreq.getPathInfo() );
+ assertEquals( "/org", wrapreq.getRequestURI() );
+ }
+
+ public void testLonger()
+ throws Exception
+ {
+ HttpServletRequest request = createHttpServletGetRequest( "http://machine.com/repository/"
+ + "org/codehaus/plexus/webdav/plexus-webdav-simple/1.0-alpha-3/plexus-webdav-simple-1.0-alpha-3.jar" );
+
+ WrappedRepositoryRequest wrapreq = new WrappedRepositoryRequest( request );
+ assertNotNull( wrapreq );
+
+ assertEquals( "/repository", wrapreq.getServletPath() );
+
+ String expected = "/org/codehaus/plexus/webdav/plexus-webdav-simple/1.0-alpha-3/plexus-webdav-simple-1.0-alpha-3.jar";
+ assertEquals( expected, wrapreq.getPathInfo() );
+ assertEquals( expected, wrapreq.getRequestURI() );
+ }
+}
diff --git a/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml
new file mode 100644
index 000000000..323862265
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<!--
+ ~ 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.
+ -->
+
+<plexus>
+ <components>
+
+ <component>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
+ <role-hint>simple</role-hint>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
+ <description>DefaultDavServerManager</description>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>simple</role-hint>
+ </requirement>
+ </requirements>
+ </component>
+
+ </components>
+</plexus>
diff --git a/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml
new file mode 100644
index 000000000..323862265
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<!--
+ ~ 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.
+ -->
+
+<plexus>
+ <components>
+
+ <component>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
+ <role-hint>simple</role-hint>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
+ <description>DefaultDavServerManager</description>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>simple</role-hint>
+ </requirement>
+ </requirements>
+ </component>
+
+ </components>
+</plexus>
diff --git a/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml
new file mode 100644
index 000000000..323862265
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<!--
+ ~ 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.
+ -->
+
+<plexus>
+ <components>
+
+ <component>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
+ <role-hint>simple</role-hint>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
+ <description>DefaultDavServerManager</description>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>simple</role-hint>
+ </requirement>
+ </requirements>
+ </component>
+
+ </components>
+</plexus>
diff --git a/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml
new file mode 100644
index 000000000..323862265
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<!--
+ ~ 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.
+ -->
+
+<plexus>
+ <components>
+
+ <component>
+ <role>org.apache.maven.archiva.webdav.DavServerManager</role>
+ <role-hint>simple</role-hint>
+ <implementation>org.apache.maven.archiva.webdav.DefaultDavServerManager</implementation>
+ <description>DefaultDavServerManager</description>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.archiva.webdav.DavServerComponent</role>
+ <role-hint>simple</role-hint>
+ </requirement>
+ </requirements>
+ </component>
+
+ </components>
+</plexus>
diff --git a/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml b/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..93634b40e
--- /dev/null
+++ b/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ ~ 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.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <display-name>Apache Archiva</display-name>
+
+ <listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+ </listener>
+
+ <context-param>
+ <param-name>contextClass</param-name>
+ <param-value>org.codehaus.plexus.spring.PlexusWebApplicationContext</param-value>
+ </context-param>
+
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>
+ classpath*:/META-INF/plexus/components.xml
+ </param-value>
+ </context-param>
+
+</web-app>
diff --git a/archiva-web/pom.xml b/archiva-web/pom.xml
index 082817ba1..21aa1d08d 100644
--- a/archiva-web/pom.xml
+++ b/archiva-web/pom.xml
@@ -33,6 +33,7 @@
<module>archiva-applet</module>
<module>archiva-security</module>
<module>archiva-webapp</module>
+ <module>archiva-webdav</module>
<module>archiva-standalone</module>
</modules>
diff --git a/pom.xml b/pom.xml
index 78cc28fe1..330967420 100644
--- a/pom.xml
+++ b/pom.xml
@@ -159,8 +159,7 @@
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <version>1.4.3</version>
+ <artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@@ -311,9 +310,9 @@
<version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.4.3</version>
+ <groupId>org.apache.maven.archiva</groupId>
+ <artifactId>archiva-webdav</artifactId>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
@@ -406,7 +405,7 @@
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
- <version>1.2.8</version>
+ <version>1.2.14</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
@@ -651,16 +650,6 @@
</exclusions>
</dependency>
<dependency>
- <groupId>org.codehaus.plexus.webdav</groupId>
- <artifactId>plexus-webdav-simple</artifactId>
- <version>1.0-beta-2</version>
- </dependency>
- <dependency>
- <groupId>org.codehaus.plexus.webdav</groupId>
- <artifactId>plexus-webdav-api</artifactId>
- <version>1.0-beta-2</version>
- </dependency>
- <dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
@@ -671,6 +660,16 @@
<version>2.2.1</version>
</dependency>
<dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.5.0</version>
+ </dependency>
+ <dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
@@ -748,6 +747,7 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.2</version>
<configuration>
<source>1.5</source>
<aggregate>true</aggregate>