From 63d0b86f690a873e3e8acaa7b3929a79d79b77d5 Mon Sep 17 00:00:00 2001 From: Brett Porter Date: Thu, 13 Mar 2008 18:28:26 +0000 Subject: [PATCH] improvements to the Spring support Bring in plexus-webdav code, from Plexus SVN r7233. The authors of this code are all committers at Maven: brett, evenisse, handyande, jesse, joakime, trygvis. This also includes the it.could WebDAV library, which retains it's original license (Apache License 2.0). git-svn-id: https://svn.apache.org/repos/asf/maven/archiva/branches@636822 13f79535-47bb-0310-9956-ffa450edef68 --- springy/archiva-base/archiva-common/pom.xml | 6 - .../database/AbstractDatabaseCleanupTest.java | 8 +- ...uceneCleanupRemoveIndexedConsumerTest.java | 4 +- .../converter/RepositoryConverterTest.java | 4 +- springy/archiva-base/archiva-policies/pom.xml | 2 +- .../proxy/CacheFailuresTransferTest.xml | 2 +- .../archiva/proxy/ChecksumTransferTest.xml | 2 +- .../proxy/ManagedDefaultTransferTest.xml | 2 +- .../proxy/ManagedLegacyTransferTest.xml | 2 +- .../archiva/proxy/MetadataTransferTest.xml | 2 +- .../archiva/proxy/RelocateTransferTest.xml | 2 +- .../archiva/proxy/SnapshotTransferTest.xml | 2 +- springy/archiva-web/archiva-webapp/pom.xml | 12 +- .../web/repository/ArchivaMimeTypeLoader.java | 2 +- .../web/repository/ProxiedDavServer.java | 37 +- .../web/repository/RepositoryServlet.java | 98 +- .../resources/META-INF/plexus/application.xml | 17 +- .../src/main/webapp/WEB-INF/web.xml | 1 + .../web/action/AbstractWebworkTestCase.java | 5 +- .../AddManagedRepositoryActionTest.java | 7 +- .../AddRemoteRepositoryActionTest.java | 5 +- .../DeleteManagedRepositoryActionTest.java | 12 +- .../DeleteRemoteRepositoryActionTest.java | 5 +- .../EditManagedRepositoryActionTest.java | 7 +- .../EditRemoteRepositoryActionTest.java | 5 +- .../repositories/RepositoriesActionTest.java | 4 +- .../AbstractRepositoryServletTestCase.java | 54 +- .../repository/ArchivaMimeTypeLoaderTest.java | 2 +- ...yServletProxiedMetadataRemoteOnlyTest.java | 2 +- .../web/repository/RepositoryServletTest.java | 7 +- .../UnauthenticatedRepositoryServlet.java | 2 +- .../resources/META-INF/plexus/components.xml | 4 +- .../src/test/resources/log4j.xml | 8 +- .../proxy/AddProxyConnectorActionTest.xml | 2 +- .../proxy/EditProxyConnectorActionTest.xml | 2 +- .../AbstractManagedRepositoriesActionTest.xml | 2 +- .../DeleteManagedRepositoryActionTest.xml | 2 +- .../RepositoryServletSecurityTest.xml | 4 +- .../web/repository/RepositoryServletTest.xml | 13 +- .../src/test/webapp/WEB-INF/web.xml | 45 + .../archiva-webdav/README-it.could-webdav.txt | 8 + springy/archiva-web/archiva-webdav/pom.xml | 101 ++ .../main/java/it/could/util/StreamTools.java | 125 ++ .../main/java/it/could/util/StringTools.java | 214 ++++ .../it/could/util/encoding/Encodable.java | 48 + .../it/could/util/encoding/EncodingAware.java | 37 + .../it/could/util/encoding/EncodingTools.java | 274 +++++ .../java/it/could/util/encoding/package.html | 61 + .../java/it/could/util/http/HttpClient.java | 1070 +++++++++++++++++ .../java/it/could/util/http/WebDavClient.java | 901 ++++++++++++++ .../main/java/it/could/util/http/package.html | 12 + .../java/it/could/util/location/Location.java | 805 +++++++++++++ .../it/could/util/location/Parameters.java | 474 ++++++++ .../java/it/could/util/location/Path.java | 559 +++++++++ .../java/it/could/util/location/package.html | 29 + .../main/java/it/could/util/location/url.gif | Bin 0 -> 18130 bytes .../main/java/it/could/util/location/url.pdf | 884 ++++++++++++++ .../src/main/java/it/could/util/package.html | 11 + .../java/it/could/webdav/DAVException.java | 132 ++ .../java/it/could/webdav/DAVInputStream.java | 165 +++ .../java/it/could/webdav/DAVListener.java | 46 + .../main/java/it/could/webdav/DAVLogger.java | 86 ++ .../main/java/it/could/webdav/DAVMethod.java | 41 + .../java/it/could/webdav/DAVMultiStatus.java | 149 +++ .../java/it/could/webdav/DAVNotModified.java | 56 + .../java/it/could/webdav/DAVOutputStream.java | 187 +++ .../java/it/could/webdav/DAVProcessor.java | 92 ++ .../java/it/could/webdav/DAVRepository.java | 164 +++ .../java/it/could/webdav/DAVResource.java | 514 ++++++++ .../main/java/it/could/webdav/DAVServlet.java | 280 +++++ .../java/it/could/webdav/DAVTransaction.java | 280 +++++ .../java/it/could/webdav/DAVUtilities.java | 420 +++++++ .../java/it/could/webdav/XMLRepository.java | 113 ++ .../java/it/could/webdav/methods/COPY.java | 71 ++ .../java/it/could/webdav/methods/DELETE.java | 54 + .../java/it/could/webdav/methods/GET.java | 144 +++ .../java/it/could/webdav/methods/HEAD.java | 79 ++ .../java/it/could/webdav/methods/MKCOL.java | 57 + .../java/it/could/webdav/methods/MOVE.java | 80 ++ .../java/it/could/webdav/methods/OPTIONS.java | 52 + .../it/could/webdav/methods/PROPFIND.java | 122 ++ .../it/could/webdav/methods/PROPPATCH.java | 55 + .../java/it/could/webdav/methods/PUT.java | 72 ++ .../java/it/could/webdav/methods/package.html | 12 + .../main/java/it/could/webdav/package.html | 134 +++ .../could/webdav/replication/DAVReplica.java | 242 ++++ .../webdav/replication/DAVReplicator.java | 131 ++ .../it/could/webdav/replication/package.html | 12 + .../webdav/AbstractDavServerComponent.java | 159 +++ .../archiva/webdav/DavServerComponent.java | 143 +++ .../archiva/webdav/DavServerException.java | 51 + .../archiva/webdav/DavServerListener.java | 39 + .../archiva/webdav/DavServerManager.java | 74 ++ .../webdav/DefaultDavServerManager.java | 88 ++ .../webdav/servlet/AbstractWebDavServlet.java | 164 +++ .../webdav/servlet/DavServerRequest.java | 39 + .../servlet/basic/BasicDavServerRequest.java | 67 ++ .../servlet/basic/BasicWebDavServlet.java | 142 +++ .../MultiplexedDavServerRequest.java | 119 ++ .../multiplexed/MultiplexedWebDavServlet.java | 137 +++ .../webdav/simple/HackedMoveMethod.java | 130 ++ .../webdav/simple/ReplacementGetMethod.java | 303 +++++ .../simple/SimpleDavServerComponent.java | 185 +++ .../maven/archiva/webdav/util/MimeTypes.java | 191 +++ .../archiva/webdav/util/WebdavMethodUtil.java | 66 + .../webdav/util/WrappedRepositoryRequest.java | 184 +++ .../org/betaversion/webdav/DAVServlet.java | 57 + .../java/org/betaversion/webdav/package.html | 15 + .../maven/archiva/webdav/util/mime-types.txt | 128 ++ .../main/resources/plexus-webdav/mime.types | 127 ++ .../main/resources/plexus-webdav/webdav.props | 13 + .../webdav/TestableHttpServletRequest.java | 495 ++++++++ .../MultiplexedDavServerRequestTest.java | 67 ++ .../SimpleDavServerComponentBasicTest.java | 38 + ...SimpleDavServerComponentIndexHtmlTest.java | 38 + .../SimpleDavServerComponentMultiTest.java | 38 + .../webdav/simple/SimpleWebdavServer.java | 51 + .../AbstractBasicWebdavProviderTestCase.java | 255 ++++ .../AbstractMultiWebdavProviderTestCase.java | 203 ++++ .../test/AbstractWebdavIndexHtmlTestCase.java | 148 +++ .../test/AbstractWebdavProviderTestCase.java | 401 ++++++ .../webdav/test/AbstractWebdavServer.java | 267 ++++ .../webdav/test/TestMultiWebDavServlet.java | 46 + .../archiva/webdav/util/MimeTypesTest.java | 42 + .../util/WrappedRepositoryRequestTest.java | 75 ++ .../SimpleDavServerComponentBasicTest.xml | 38 + .../SimpleDavServerComponentIndexHtmlTest.xml | 38 + .../SimpleDavServerComponentMultiTest.xml | 38 + .../webdav/simple/SimpleWebdavServer.xml | 38 + .../src/test/webapp/WEB-INF/web.xml | 43 + springy/archiva-web/pom.xml | 1 + springy/pom.xml | 25 +- 132 files changed, 14856 insertions(+), 209 deletions(-) create mode 100644 springy/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml create mode 100644 springy/archiva-web/archiva-webdav/README-it.could-webdav.txt create mode 100644 springy/archiva-web/archiva-webdav/pom.xml create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html create mode 100644 springy/archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt create mode 100644 springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types create mode 100644 springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java create mode 100644 springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml create mode 100644 springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml create mode 100644 springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml create mode 100644 springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml create mode 100644 springy/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml diff --git a/springy/archiva-base/archiva-common/pom.xml b/springy/archiva-base/archiva-common/pom.xml index dee747060..df9ba4765 100644 --- a/springy/archiva-base/archiva-common/pom.xml +++ b/springy/archiva-base/archiva-common/pom.xml @@ -50,12 +50,6 @@ org.codehaus.plexus plexus-slf4j-logging - org.codehaus.plexus plexus-spring diff --git a/springy/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java b/springy/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java index 33f4a62a0..177e74ad7 100644 --- a/springy/archiva-base/archiva-consumers/archiva-database-consumers/src/test/java/org/apache/maven/archiva/consumers/database/AbstractDatabaseCleanupTest.java +++ b/springy/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.codehaus.plexus.util.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 org.codehaus.plexus.util.FileUtils; import java.io.File; @@ -35,7 +35,7 @@ import java.io.File; * @author Maria Odea Ching */ public abstract class AbstractDatabaseCleanupTest - extends PlexusTestCase + extends PlexusInSpringTestCase { ArchivaConfiguration archivaConfig; diff --git a/springy/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java b/springy/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java index 64eeae2bd..22070f151 100644 --- a/springy/archiva-base/archiva-consumers/archiva-lucene-consumers/src/test/java/org/apache/maven/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumerTest.java +++ b/springy/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/springy/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java b/springy/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java index 70a5ca1ff..4dbc2a434 100644 --- a/springy/archiva-base/archiva-converter/src/test/java/org/apache/maven/archiva/converter/RepositoryConverterTest.java +++ b/springy/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/springy/archiva-base/archiva-policies/pom.xml b/springy/archiva-base/archiva-policies/pom.xml index 64ed1af39..3466958b3 100644 --- a/springy/archiva-base/archiva-policies/pom.xml +++ b/springy/archiva-base/archiva-policies/pom.xml @@ -35,7 +35,7 @@ org.codehaus.plexus plexus-spring - 1.0-SNAPSHOT + 1.0-SNAPSHOT commons-lang diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml index 4b20ddc99..848a73057 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/CacheFailuresTransferTest.xml @@ -112,4 +112,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml index 19ca39096..9d90e2250 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ChecksumTransferTest.xml @@ -110,4 +110,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml index 19ca39096..9d90e2250 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedDefaultTransferTest.xml @@ -110,4 +110,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml index 19ca39096..9d90e2250 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/ManagedLegacyTransferTest.xml @@ -110,4 +110,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml index 80572a87f..e7b75187c 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/MetadataTransferTest.xml @@ -133,4 +133,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml index 19ca39096..9d90e2250 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/RelocateTransferTest.xml @@ -110,4 +110,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml index 19ca39096..9d90e2250 100644 --- a/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml +++ b/springy/archiva-base/archiva-proxy/src/test/resources/org/apache/maven/archiva/proxy/SnapshotTransferTest.xml @@ -110,4 +110,4 @@ basic - \ No newline at end of file + diff --git a/springy/archiva-web/archiva-webapp/pom.xml b/springy/archiva-web/archiva-webapp/pom.xml index d6898ccf0..07ad5f1e0 100644 --- a/springy/archiva-web/archiva-webapp/pom.xml +++ b/springy/archiva-web/archiva-webapp/pom.xml @@ -180,8 +180,8 @@ - org.codehaus.plexus.webdav - plexus-webdav-simple + org.apache.maven.archiva + archiva-webdav org.codehaus.plexus @@ -224,6 +224,14 @@ xmlunit xmlunit + + org.codehaus.plexus + plexus-quartz + + + org.codehaus.plexus + plexus-taskqueue + org.codehaus.plexus.redback redback-keys-memory diff --git a/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java b/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java index 2a821848b..b289188ed 100644 --- a/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoader.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java b/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java index 027f58f54..bafea90af 100644 --- a/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/ProxiedDavServer.java +++ b/springy/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 Joakim Erdfelt * @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/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java b/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java index f0fed7486..c3121a2cb 100644 --- a/springy/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/repository/RepositoryServlet.java +++ b/springy/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() ); @@ -108,45 +108,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/springy/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml b/springy/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml index aed45580b..d03a07f82 100644 --- a/springy/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml +++ b/springy/archiva-web/archiva-webapp/src/main/resources/META-INF/plexus/application.xml @@ -82,13 +82,16 @@ - org.codehaus.plexus.webdav.DavServerManager + org.apache.maven.archiva.webdav.DavServerManager default - org.codehaus.plexus.webdav.DefaultDavServerManager + org.apache.maven.archiva.webdav.DefaultDavServerManager DefaultDavServerManager - - proxied - + + + org.apache.maven.archiva.webdav.DavServerComponent + proxied + + @@ -200,8 +203,8 @@ - org.codehaus.plexus.webdav.util.MimeTypes - org.codehaus.plexus.webdav.util.MimeTypes + org.apache.maven.archiva.webdav.util.MimeTypes + org.apache.maven.archiva.webdav.util.MimeTypes MimeTypes archiva-mime-types.txt diff --git a/springy/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml b/springy/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml index 9cb6f1420..c67947e24 100644 --- a/springy/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml +++ b/springy/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/web.xml @@ -68,6 +68,7 @@ contextConfigLocation 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 diff --git a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java index bfbd6ddbe..35f526e4f 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/AbstractWebworkTestCase.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java index 8a870cc5c..ed0501636 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddManagedRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java index c7beec7ca..5089b07a3 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/AddRemoteRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java index c0582812c..f9e205f4c 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java index a6b5b7eaa..1457f5952 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/DeleteRemoteRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java index 407ffd710..c109a8eaa 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditManagedRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java index cd1a9018d..eb8a92920 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/EditRemoteRepositoryActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java index 375a142d0..1b372a1da 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesActionTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java index b08ca9328..bfd363edc 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java +++ b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/AbstractRepositoryServletTestCase.java @@ -22,20 +22,15 @@ 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.spring.PlexusInSpringTestCase; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.xml.XmlBeanFactory; -import org.springframework.core.io.ClassPathResource; -import javax.servlet.ServletContext; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import java.io.File; import java.io.IOException; @@ -52,12 +47,18 @@ public abstract class AbstractRepositoryServletTestCase 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 { @@ -134,7 +135,7 @@ public abstract class AbstractRepositoryServletTestCase } } - protected void saveConfiguration() + protected void saveConfiguration( ArchivaConfiguration archivaConfiguration ) throws Exception { archivaConfiguration.save( archivaConfiguration.getConfiguration() ); @@ -145,30 +146,25 @@ public abstract class AbstractRepositoryServletTestCase { super.setUp(); - try - { - String appserverBase = getTestFile( "target/appserver-base" ).getAbsolutePath(); - System.setProperty( "appserver.base", appserverBase ); + String appserverBase = getTestFile( "target/appserver-base" ).getAbsolutePath(); + System.setProperty( "appserver.base", appserverBase ); - File testConf = getTestFile( "src/test/resources/repository-archiva.xml" ); - File testConfDest = new File( appserverBase, "conf/archiva.xml" ); - FileUtils.copyFile( testConf, testConfDest ); + File testConf = getTestFile( "src/test/resources/repository-archiva.xml" ); + File testConfDest = new File( appserverBase, "conf/archiva.xml" ); + FileUtils.copyFile( testConf, testConfDest ); - archivaConfiguration = (ArchivaConfiguration) lookup( ArchivaConfiguration.class ); - repoRootInternal = new File( appserverBase, "data/repositories/internal" ); - Configuration config = archivaConfiguration.getConfiguration(); + archivaConfiguration = (ArchivaConfiguration) lookup( ArchivaConfiguration.class ); + repoRootInternal = new File( appserverBase, "data/repositories/internal" ); + Configuration config = archivaConfiguration.getConfiguration(); - config.addManagedRepository( createManagedRepository( REPOID_INTERNAL, "Internal Test Repo", repoRootInternal ) ); - saveConfiguration(); + config.addManagedRepository( createManagedRepository( REPOID_INTERNAL, "Internal Test Repo", repoRootInternal ) ); + saveConfiguration( archivaConfiguration ); - sr = new ServletRunner(); - sr.registerServlet( "/repository/*", UnauthenticatedRepositoryServlet.class.getName() ); - sc = sr.newClient(); - } - finally - { - tearDown(); - } + CacheManager.getInstance().removeCache( "url-failures-cache" ); + + sr = new ServletRunner( getTestFile( "src/test/webapp/WEB-INF/web.xml" ) ); + sr.registerServlet( "/repository/*", UnauthenticatedRepositoryServlet.class.getName() ); + sc = sr.newClient(); } @Override diff --git a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java index 5d634e990..848e8cf73 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/ArchivaMimeTypeLoaderTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java index 63eaf2416..d649740eb 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletProxiedMetadataRemoteOnlyTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java index 71ff228b0..1c5159849 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/RepositoryServletTest.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java b/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java index 7e5f063cf..3d630aaa6 100644 --- a/springy/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/repository/UnauthenticatedRepositoryServlet.java +++ b/springy/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/springy/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml b/springy/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml index 1cb21906f..921c1cca1 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/META-INF/plexus/components.xml @@ -22,8 +22,8 @@ - org.codehaus.plexus.webdav.util.MimeTypes - org.codehaus.plexus.webdav.util.MimeTypes + org.apache.maven.archiva.webdav.util.MimeTypes + org.apache.maven.archiva.webdav.util.MimeTypes MimeTypes archiva-mime-types.txt diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/log4j.xml b/springy/archiva-web/archiva-webapp/src/test/resources/log4j.xml index ad3933785..a2e7ea23d 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/log4j.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/log4j.xml @@ -23,8 +23,12 @@ - - + + + + + + diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml index 6f4fb4247..924f07f7c 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/AddProxyConnectorActionTest.xml @@ -24,7 +24,7 @@ org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager basic - + org.codehaus.plexus.cache.Cache url-failures-cache diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml index 6f4fb4247..924f07f7c 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/connectors/proxy/EditProxyConnectorActionTest.xml @@ -24,7 +24,7 @@ org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager basic - + org.codehaus.plexus.cache.Cache url-failures-cache diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml index 7273543eb..ba213da8e 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/AbstractManagedRepositoriesActionTest.xml @@ -24,7 +24,7 @@ org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager basic - + com.opensymphony.xwork.Action addManagedRepositoryAction diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml index a5891aacc..43c1eae54 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/admin/repositories/DeleteManagedRepositoryActionTest.xml @@ -19,7 +19,7 @@ - + org.codehaus.plexus.logging.LoggerManager org.codehaus.plexus.logging.slf4j.Slf4jLoggerManager basic diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml index 15ea6ed1a..d7087095a 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletSecurityTest.xml @@ -64,9 +64,9 @@ - org.codehaus.plexus.webdav.DavServerManager + org.apache.maven.archiva.webdav.DavServerManager default - org.codehaus.plexus.webdav.DefaultDavServerManager + org.apache.maven.archiva.webdav.DefaultDavServerManager DefaultDavServerManager proxied diff --git a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml index 4f0ff5531..3b3b20396 100644 --- a/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml +++ b/springy/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml @@ -64,13 +64,16 @@ - org.codehaus.plexus.webdav.DavServerManager + org.apache.maven.archiva.webdav.DavServerManager default - org.codehaus.plexus.webdav.DefaultDavServerManager + org.apache.maven.archiva.webdav.DefaultDavServerManager DefaultDavServerManager - - proxied - + + + org.apache.maven.archiva.webdav.DavServerComponent + proxied + + diff --git a/springy/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml b/springy/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..f47bd657f --- /dev/null +++ b/springy/archiva-web/archiva-webapp/src/test/webapp/WEB-INF/web.xml @@ -0,0 +1,45 @@ + + + + + + Apache Archiva + + + org.springframework.web.context.ContextLoaderListener + + + + contextClass + org.codehaus.plexus.spring.PlexusWebApplicationContext + + + + contextConfigLocation + + classpath*:/META-INF/plexus/components.xml + classpath*:/META-INF/spring-context.xml + target/test-classes/org/apache/maven/archiva/web/repository/RepositoryServletTest.xml + + + + diff --git a/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt b/springy/archiva-web/archiva-webdav/README-it.could-webdav.txt new file mode 100644 index 000000000..5206a3429 --- /dev/null +++ b/springy/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 from the POM +- replace the servlet-api dependency in the POM with it.could webdav. diff --git a/springy/archiva-web/archiva-webdav/pom.xml b/springy/archiva-web/archiva-webdav/pom.xml new file mode 100644 index 000000000..fa863c017 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/pom.xml @@ -0,0 +1,101 @@ + + + + + 4.0.0 + + org.apache.maven.archiva + archiva-web + 1.1-SNAPSHOT + + + archiva-webdav + Archiva WebDAV Provider + + + + commons-lang + commons-lang + + + org.codehaus.plexus + plexus-component-api + + + org.codehaus.plexus + plexus-spring + 1.0-SNAPSHOT + + + org.springframework + spring-web + 2.5.1 + + + commons-io + commons-io + + + + + + javax.servlet + servlet-api + 2.3 + provided + + + slide + slide-webdavlib + 2.1 + test + + + org.mortbay.jetty + jetty + 6.0.2 + test + + + + + + + + src/main/resources + + + ${project.build.sourceDirectory} + + **/*.java + **/package.html + **/url.gif + **/url.pdf + + + + + diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java new file mode 100644 index 000000000..17ebb7e9c --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StreamTools.java @@ -0,0 +1,125 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

An utility class providing various static methods operating on + * {@link InputStream input} and {@link OutputStream output} streams.

+ * + * @author Pier Fumagalli + */ +public final class StreamTools { + + /**

Deny construction.

*/ + private StreamTools() { }; + + /** + *

Copy every byte from the specified {@link InputStream} to the specifed + * {@link OutputStream} and then close both of them.

+ * + *

This method is equivalent to a call to the following method: + * {@link #copy(InputStream,OutputStream,boolean) copy(in, out, true)}.

+ * + * @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); + } + + /** + *

Copy every byte from the specified {@link InputStream} to the specifed + * {@link OutputStream} and then optionally close both of them.

+ * + * @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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java new file mode 100644 index 000000000..3b0eb14ea --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/StringTools.java @@ -0,0 +1,214 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

An utility class providing various static methods operating on + * {@link String}s.

+ * + *

This class implement the {@link Encodable} interface from which it + * inherits its {@link Encodable#DEFAULT_ENCODING default encoding}.

+ * + * @author Pier Fumagalli + */ +public final class StringTools { + + /**

The {@link SimpleDateFormat} RFC-822 date format.

*/ + private static final String FORMAT_822 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; + /**

The {@link SimpleDateFormat} RFC-822 date format.

*/ + private static final String FORMAT_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + /**

The {@link TimeZone} to use for dates.

*/ + private static final TimeZone TIMEZONE = TimeZone.getTimeZone("GMT"); + /**

The {@link Locale} to use for dates.

*/ + private static final Locale LOCALE = Locale.US; + + /**

Deny construction.

*/ + private StringTools() { } + + /* ====================================================================== */ + /* NUMBER AND DATE PARSING AND FORMATTING */ + /* ====================================================================== */ + + /** + *

Format a {@link Number} into a {@link String} making sure that + * {@link NullPointerException}s are not thrown.

+ * + * @param number the {@link Number} to format. + * @return a {@link String} instance or null if the object was null. + */ + public static String formatNumber(Number number) { + if (number == null) return null; + return (number.toString()); + } + + /** + *

Parse a {@link String} into a {@link Long}.

+ * + * @param string the {@link String} to parse. + * @return a {@link Long} instance or null 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; + } + } + + /** + *

Format a {@link Date} according to the HTTP/1.1 RFC.

+ * + * @param date the {@link Date} to format. + * @return a {@link String} instance or null 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); + } + + /** + *

Format a {@link Date} according to the ISO 8601 specification.

+ * + * @param date the {@link Date} to format. + * @return a {@link String} instance or null 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); + } + + /** + *

Parse a {@link String} into a {@link Date} according to the + * HTTP/1.1 RFC (Mon, 31 Jan 2000 11:59:00 GMT).

+ * + * @param string the {@link String} to parse. + * @return a {@link Date} instance or null 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; + } + } + + /** + *

Parse a {@link String} into a {@link Date} according to the ISO 8601 + * specification (2000-12-31T11:59:00Z).

+ * + * @param string the {@link String} to parse. + * @return a {@link Date} instance or null 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 */ + /* ====================================================================== */ + + /** + *

Split the specified string in two parts according to the specified + * delimiter, and any resulting path of zero length will be converted to + * null.

+ */ + 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) }; + } + } + } + + /** + *

Split the specified string according to the specified delimiter, and + * any resulting path of zero length will be converted to null.

+ */ + 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()]); + } + + /** + *

Find the first occurrence of one of the specified delimiter characters + * in the specified source string.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java new file mode 100644 index 000000000..2f644f9a0 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/Encodable.java @@ -0,0 +1,48 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

The {@link Encodable} interface describes an {@link Object} whose + * {@link String} representation can vary depending on the encoding used.

+ * + * @author Pier Fumagalli + */ +public interface Encodable extends EncodingAware { + + /** + *

Return the {@link String} representation of this instance.

+ * + *

This method is equivalent to a call to + * {@link #toString(String) toString}({@link EncodingAware#DEFAULT_ENCODING + * DEFAULT_ENCODING})

+ */ + public String toString(); + + /** + *

Return the {@link String} representation of this instance given + * a specific character encoding.

+ * + * @throws UnsupportedEncodingException if the specified encoding is not + * supported by the platform. + */ + public String toString(String encoding) + throws UnsupportedEncodingException; + +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java new file mode 100644 index 000000000..0690a175b --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingAware.java @@ -0,0 +1,37 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

The {@link EncodingAware} interface describes an {@link Object} aware + * of multiple encodings existing withing the platform.

+ * + * @author Pier Fumagalli + */ +public interface EncodingAware { + + /**

The default encoding is specified as being UTF-8.

*/ + public static final String DEFAULT_ENCODING = "UTF-8"; + + /**

The platform encoding is evaluated at runtime from the JVM.

*/ + public static final String PLATFORM_ENCODING = + new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding(); + +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java new file mode 100644 index 000000000..c0c2e7adb --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/EncodingTools.java @@ -0,0 +1,274 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

An utility class providing various static methods dealing with + * encodings and {@link Encodable} objects..

+ * + * @author Pier Fumagalli + */ +public final class EncodingTools implements EncodingAware { + + /**

The Base-64 alphabet.

*/ + 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', '+', '/', '=' }; + + /**

Deny construction of this class.

*/ + private EncodingTools() { } + + /* ====================================================================== */ + /* URL ENCODING / DECODING */ + /* ====================================================================== */ + + /** + *

Return the {@link String} representation of the specified + * {@link Encodable} object using the {@link EncodingAware#DEFAULT_ENCODING + * default encoding}.

+ * + * throws NullPointerException if the {@link Encodable} was null. + */ + 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 */ + /* ====================================================================== */ + + /** + *

URL-encode the specified string.

+ */ + 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); + } + + /** + *

URL-encode the specified string.

+ */ + 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); + } + } + + /** + *

URL-decode the specified string.

+ */ + 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); + } + + /** + *

URL-decode the specified string.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Encode the specified string in base 64 using the specified + * encoding.

+ */ + 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); + } + + /** + *

Encode the specified string in base 64 using the default encoding.

+ */ + 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); + } + } + + /** + *

Decode the specified base 64 string using the specified encoding.

+ */ + 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)); + } + + /** + *

Decode the specified base 64 string using the default encoding.

+ */ + 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); + } + } + + /* ====================================================================== */ + + /**

Retrieve the offset of a character in the base 64 alphabet.

*/ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html new file mode 100644 index 000000000..675ba3f58 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/encoding/package.html @@ -0,0 +1,61 @@ + + + Encoding Utilities + + +

+ This package contains a number of utility classes dealing with generic + encoding of {@link java.lang.String}s. +

+

+ 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. +

+

+ Let's consider (as an example) the URL encoded {@link java.lang.String} + %C2%A3 100 can be easily decomposed in a byte array using + URL decoding techniques: we would end up with the following byte array: + 0x0C2 0x0A3 0x20 0x31 0x30 0x30. +

+

+ 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). +

+

+ So, for example, if we were to decode the above mentioned byte array using + the ISO-8859-1 encoding, we would obtain the string + "£ 100", or in details: +

+
    +
  • a latin capital letter "A" with a circumflex accent
  • +
  • the pound sign
  • +
  • a space
  • +
  • the number 1
  • +
  • the number 0
  • +
  • the number 0
  • +
+

+ If we were to decode the same byte sequence using UTF-8, on the + other hand, we would obtain the (quite different) string + "£ 100", or in details: +

+
    +
  • the pound sign
  • +
  • a space
  • +
  • the number 1
  • +
  • the number 0
  • +
  • the number 0
  • +
+

+ 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. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java new file mode 100644 index 000000000..75884e0a2 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/HttpClient.java @@ -0,0 +1,1070 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A class implementing an extremely simple HTTP 1.0 connector with + * basic authentication support.

+ * + * @author Pier Fumagalli + */ +public class HttpClient { + + /**

The default HTTP method to use.

*/ + public static final String DEFAULT_METHOD = "GET"; + + /* ====================================================================== */ + + /**

The byte sequence CR LF (the end of the request).

*/ + private static final byte CRLF[] = { 0x0d, 0x0a }; + /**

The byte sequence for " HTTP/1.0\r\n" (the request signature).

*/ + private static final byte HTTP[] = { 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, + 0x31, 0x2e, 0x30, 0x0d, 0x0a }; + + /* ====================================================================== */ + + /**

The buffer used to parse lines in the response.

*/ + private final byte buffer[] = new byte[4096]; + /**

The map of the current request headers.

*/ + private final Map requestHeaders = new HashMap(); + /**

The map of the current response headers.

*/ + private final Map responseHeaders = new HashMap(); + + /* ====================================================================== */ + + /**

The {@link Location} pointing to the current request.

*/ + private Location location; + /**

The status of the current request.

*/ + private Status status = null; + /**

An array of acceptable statuses to verify upon connection.

*/ + private int acceptable[] = null; + + /* ====================================================================== */ + + /**

The limited input stream associated with this request.

*/ + private Input xinput = null; + /**

The limited output stream associated with this request.

*/ + private Output xoutput = null; + /**

The socket associated with this request.

*/ + private Socket xsocket = null; + + /* ====================================================================== */ + + /** + *

Create a new {@link HttpClient} instance associated with the + * specified location in string format.

+ * + * @throws MalformedURLException if the location couldn't be parsed. + */ + public HttpClient(String location) + throws MalformedURLException { + this.location = Location.parse(location); + } + + /** + *

Create a new {@link HttpClient} instance associated with the + * specified location in string format.

+ * + * @throws MalformedURLException if the location couldn't be parsed. + */ + public HttpClient(String location, String encoding) + throws MalformedURLException, UnsupportedEncodingException { + this.location = Location.parse(location, encoding); + } + + /** + *

Create a new {@link HttpClient} instance associated with the + * specified {@link Location}.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Set an HTTP response status code considered to be acceptable when + * verifying the connection.

+ */ + public HttpClient setAcceptableStatus(int status) { + return this.setAcceptableStatuses(new int[] { status }); + } + + /** + *

Set an array of HTTP response status codes considered to be acceptable + * when verifying the connection.

+ * + *

If the array is null status code checking is disabled.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Connect to the {@link Location} specified at construction using the + * default method GET.

+ * + *

This is equivalent to {@link #connect(boolean) connect(true)}.

+ * + * @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); + } + + /** + *

Connect to the {@link Location} specified at construction using the + * default method GET allowing for a specified amount of + * content to be written into the request.

+ * + * @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); + } + + /** + *

Connect to the {@link Location} specified at construction using the + * default method GET and optionally following redirects.

+ * + * @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); + } + + /** + *

Connect to the {@link Location} specified at construction with the + * specified method.

+ * + *

This is equivalent to {@link #connect(String,boolean) + * connect(method, true)}.

+ * + * @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); + } + + /** + *

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.

+ * + * @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); + } + + /** + *

Connect to the {@link Location} specified at construction with the + * specified method and optionally following redirects.

+ * + * @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); + } + + /** + *

Disconnect from the remote endpoint and terminate the request.

+ * + *

Note that request and response headers, the resultin status and + * acceptable statuses are not cleared by this method.

+ * + * @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); + } + + /** + *

Disconnect from the remote endpoint and terminate the request.

+ * + * @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 */ + /* ====================================================================== */ + + /** + *

Internal method actually connecting to the remote HTTP server.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return an {@link InputStream} where the content of the HTTP response + * can be read from.

+ * + * @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; + } + + /** + *

Return an {@link OutputStream} where the content of the HTTP request + * can be written to.

+ * + * @throws IllegalStateException if this instance is not connected yet or if + * upon connection the size of the request was + * not specifed or zero. + */ + 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 */ + /* ====================================================================== */ + + /** + *

Return the {@link Location} of this connection.

+ * + *

This might be different from the {@link Location} specified at + * construction time if upon connecting HTTP redirections were followed.

+ */ + public Location getLocation() { + return this.location; + } + + /** + *

Add a new header that will be sent with the HTTP request.

+ * + *

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)}.

+ * + * @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 null. + */ + public HttpClient addRequestHeader(String name, String value) { + return this.addRequestHeader(name, value, false); + } + + /** + *

Add a new header that will be sent with the HTTP request.

+ * + * @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 null. + */ + 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); + } + } + + /** + *

Remove the named header from the current HTTP request.

+ * + * @param name the name of the request header to add. + * @return this {@link HttpClient} instance. + * @throws NullPointerException the name was null. + */ + public HttpClient removeRequestHeader(String name) { + final String key = name.toLowerCase(); + this.requestHeaders.remove(key); + return this; + } + + /** + *

Remove all headers from the current HTTP request.

+ * + * @return this {@link HttpClient} instance. + */ + public HttpClient removeRequestHeaders() { + this.requestHeaders.clear(); + return this; + } + + /** + *

Return the first value for the specified response header.

+ * + * @param name the name of the header whose value needs to be returned. + * @return a {@link String} or null 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); + } + + /** + *

Return all the values for the specified response header.

+ * + * @param name the name of the header whose values needs to be returned. + * @return a {@link List} or null 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); + } + + /** + *

Return an {@link Iterator} over all response header names.

+ * + * @return a non-null {@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(); + } + }; + } + + /** + *

Return the protocol returned by the remote HTTP server.

+ * + * @return a non-null {@link String} like HTTP/1.0. + * @throws IllegalStateException if the connection was never connected. + */ + public String getResponseProtocol() { + if (this.status == null) throw new IllegalStateException(); + return this.status.protocol; + } + + /** + *

Return the status returned by the remote HTTP server.

+ * + * @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; + } + + /** + *

Return the status message returned by the remote HTTP server.

+ * + * @return a non-null {@link String} like OK. + * @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 */ + /* ====================================================================== */ + + /** + *

Read a single line of the HTTP response from the specified + * {@link InputStream} into a byte array (trailing CRLF are removed).

+ */ + 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; + } + + /** + *

Read the status line from the specified {@link InputStream} and + * setup the {@link #status} field.

+ */ + 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]); + } + + /** + *

Read all the response headers from the specified {@link InputStream} + * and setup the {@link #responseHeaders} field.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

A simple internal class representing a response status line.

+ */ + private static final class Status { + + /**

The response protocol, like HTTP/1.0 */ + private final String protocol; + /**

The response status code, like 302 */ + private final int status; + /**

The response message, like Moved permanently */ + private final String message; + + /** + *

Create a new {@link Status} verifying the supplied parameters.

+ * + * @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"); + } + } + + /** + *

A simple internal class representing a request header.

+ */ + private static final class RequestHeader { + + /**

The byte array of the header's name.

*/ + private final byte name[]; + /**

A {@link List} of all the header's values.

*/ + private final List values; + + /**

Create a new {@link RequestHeader} instance.

*/ + private RequestHeader(String name) + throws UnsupportedEncodingException { + this.name = (name + ": ").getBytes("US-ASCII"); + this.values = new ArrayList(); + } + } + + /** + *

A simple internal class representing a response header.

+ */ + private static final class ResponseHeader { + + /**

The real name of the response header.

*/ + private final String name; + /**

A {@link List} of all the header's values.

*/ + private final List values; + + /**

Create a new {@link ResponseHeader} instance.

*/ + private ResponseHeader(String name) + throws UnsupportedEncodingException { + this.name = name; + this.values = new ArrayList(); + } + } + + /* ====================================================================== */ + /* LIMITED STREAMS */ + /* ====================================================================== */ + + /** + *

A simple {@link OutputStream} writing at most the number of bytes + * specified at construction.

+ */ + private static final class Output extends OutputStream { + + /**

The {@link OutputStream} wrapped by this instance.

*/ + private final OutputStream output; + /**

The {@link InputStream} wrapped by this instance.

*/ + private final InputStream input; + /**

The {@link HttpClient} wrapped by this instance.

*/ + private final HttpClient client; + /**

The number of bytes yet to write.

*/ + private long remaining; + /**

A flag indicating whether this instance was closed.

*/ + private boolean closed; + + /** + *

Create a new {@link Output} instance with the specified limit + * of bytes to write.

+ * + * @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(); + } + } + } + + /** + *

A simple {@link InputStream} reading at most the number of bytes + * specified at construction.

+ */ + private static final class Input extends InputStream { + + /**

The {@link InputStream} wrapped by this instance.

*/ + private final InputStream input; + /**

The {@link HttpClient} wrapped by this instance.

*/ + private final HttpClient client; + /**

The number of bytes yet to write or -1 if unknown.

*/ + private long remaining; + /**

A flag indicating whether this instance was closed.

*/ + private boolean closed; + + /** + *

Create a new {@link Input} instance with the specified limit + * of bytes to read.

+ * + * @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 */ + /* ====================================================================== */ + + /** + *

Utility method: fetch the location specified on the command + * line following redirects if necessary.

+ * + *

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.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java new file mode 100644 index 000000000..fe0eba41b --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/WebDavClient.java @@ -0,0 +1,901 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A class implementing an extremely simple WebDAV Level 1 client based on + * the {@link HttpClient}.

+ * + *

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.

+ * + * @author Pier Fumagalli + */ +public class WebDavClient { + + /**

The WebDAV resource asociated with this instance.

*/ + private Resource resource; + /**

A map of children resources of this instance.

*/ + private Map children; + + /** + *

Create a new {@link WebDavClient} instance opening the collection + * identified by the specified {@link Location}.

+ * + * @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 null. + */ + public WebDavClient(Location location) + throws NullPointerException, IOException { + if (location == null) throw new NullPointerException("Null location"); + this.reload(location); + } + + /* ====================================================================== */ + /* ACTIONS */ + /* ====================================================================== */ + + /** + *

Refresh this {@link WebDavClient} instance re-connecting to the remote + * collection and re-reading its properties.

+ * + * @return this {@link WebDavClient} instance. + */ + public WebDavClient refresh() + throws IOException { + this.reload(this.resource.location); + return this; + } + + /** + *

Fetch the contents of the specified child resource of the collection + * represented by this {@link WebDavClient} instance.

+ * + * @see #isCollection(String) + * @return a non-null {@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 null. + */ + 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"); + } + + /** + *

Delete the child resource (or collection) of the collection + * represented by this {@link WebDavClient} instance.

+ * + * @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 null. + */ + 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(); + } + + /** + *

Create a new collection as a child of the collection represented + * by this {@link WebDavClient} instance.

+ * + *

In comparison to {@link #put(String)} and {@link #put(String, long)} + * this method will fail if the specified child already exist.

+ * + * @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 null. + */ + 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(); + } + + /** + *

Create a new (or update the contents of a) child of of the collection + * represented by this {@link WebDavClient} instance.

+ * + *

This method will behave exactly like the {@link #put(String, long)} + * method, but the data written to the returned {@link OutputStream} will + * be buffered in memory and will be transmitted to the remote + * server only when the {@link OutputStream#close()} method is called.

+ * + *

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.

+ * + *

Use this method in extreme cases. In normal circumstances always rely + * on the {@link #put(String, long)} method.

+ * + * @see #put(String, long) + * @return a non-null {@link OutputStream} instance. + * @throws NullPointerException if the child was null. + */ + 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(); + } + }; + } + + /** + *

Create a new (or update the contents of a) child of of the collection + * represented by this {@link WebDavClient} instance.

+ * + *

If the specified child {@link #hasChild(String) already exists} on + * the remote server, it will be {@link #delete(String) deleted} before + * writing.

+ * + * @return a non-null {@link OutputStream} instance. + * @throws NullPointerException if the child was null. + * @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(); + } + } + }; + } + + /** + *

Open the specified child collection of the collection represented by + * this {@link WebDavClient} as a new {@link WebDavClient} instance.

+ * + *

If the specified child is "." this method + * will behave exactly like {@link #refresh()} and this instance + * will be returned.

+ * + *

If the specified child is ".." this method + * will behave exactly like {@link #parent()}.

+ * + * @return a non-null {@link WebDavClient} instance. + * @throws NullPointerException if the child was null. + * @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"); + } + + /** + *

Open the parent collection of the collection represented by this + * {@link WebDavClient} as a new {@link WebDavClient} instance.

+ * + * @return a non-null {@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 */ + /* ====================================================================== */ + + /** + *

Return an {@link Iterator} over {@link String}s for all the children + * of the collection represented by this {@link WebDavClient} instance.

+ */ + public Iterator iterator() { + return this.children.keySet().iterator(); + } + + /** + *

Checks if the collection represented by this {@link WebDavClient} + * contains the specified child.

+ */ + public boolean hasChild(String child) { + return this.children.containsKey(child); + } + + /** + *

Return the {@link Location} associated with the collection + * represented by this {@link WebDavClient}.

+ * + *

The returned {@link Location} can be different from the one specified + * at construction, in case the server redirected us upon connection.

+ */ + public Location getLocation() { + return this.resource.location; + } + + /** + *

Return the content length (in bytes) of the collection represented + * by this {@link WebDavClient} as passed to us by the WebDAV server.

+ */ + public long getContentLength() { + return this.resource.contentLength; + } + + /** + *

Return the content type (mime-type) of the collection represented + * by this {@link WebDavClient} as passed to us by the WebDAV server.

+ */ + public String getContentType() { + return this.resource.contentType; + } + + /** + *

Return the last modified {@link Date} of the collection represented + * by this {@link WebDavClient} as passed to us by the WebDAV server.

+ */ + public Date getLastModified() { + return this.resource.lastModified; + } + + /** + *

Return the creation {@link Date} of the collection represented + * by this {@link WebDavClient} as passed to us by the WebDAV server.

+ */ + public Date getCreationDate() { + return this.resource.creationDate; + } + + /** + *

Return the {@link Location} associated with the specified child of + * the collection represented by this {@link WebDavClient}.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + public Location getLocation(String child) + throws IOException { + Location location = this.getResource(child).location; + return this.resource.location.resolve(location); + } + + /** + *

Checks if the specified child of the collection represented by this + * {@link WebDavClient} instance is a collection.

+ */ + public boolean isCollection(String child) + throws IOException { + return this.getResource(child).collection; + } + + /** + *

Return the content length (in bytes) associated with the specified + * child of the collection represented by this {@link WebDavClient}.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + public long getContentLength(String child) + throws IOException { + return this.getResource(child).contentLength; + } + + /** + *

Return the content type (mime-type) associated with the specified + * child of the collection represented by this {@link WebDavClient}.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + public String getContentType(String child) + throws IOException { + return this.getResource(child).contentType; + } + + /** + *

Return the last modified {@link Date} associated with the specified + * child of the collection represented by this {@link WebDavClient}.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + public Date getLastModified(String child) + throws IOException { + return this.getResource(child).lastModified; + } + + /** + *

Return the creation {@link Date} associated with the specified + * child of the collection represented by this {@link WebDavClient}.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + public Date getCreationDate(String child) + throws IOException { + return this.getResource(child).creationDate; + } + + /* ====================================================================== */ + /* INTERNAL METHODS */ + /* ====================================================================== */ + + /** + *

Return the resource associated with the specified child.

+ * + * @throws IOException if the specified child does not exist. + * @throws NullPointerException if the specified child was null. + */ + 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; + } + + /** + *

Contact the remote WebDAV server and fetch all properties.

+ */ + 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); + } + + /** + *

Contact the remote WebDAV server and do an OPTIONS lookup.

+ */ + 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"); + } + + /** + *

Contact the remote WebDAV server and do a PROPFIND lookup, returning + * a {@link List} of all scavenged resources.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

An internal XML {@link DefaultHandler} used to parse out the various + * details of a PROPFIND response.

+ */ + 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"; + + /**

The {@link Location} for resolving all other links.

*/ + private final Location base; + /**

The {@link List} of all scavenged resources.

*/ + private final List list = new ArrayList(); + /**

The resource currently being processed.

*/ + private Resource rsrc = null; + /**

A {@link StringBuffer} holding character data.

*/ + private StringBuffer buff = null; + /**

A {@link Stack} for quick-and-dirty pseudo XPath lookups.

*/ + private Stack stack = new Stack(); + + /** + *

Create a new instance specifying the base {@link Location}.

+ */ + private Handler(Location location) { + this.base = location; + } + + /** + *

Push an element name in the stack for pseudo-XPath lookups.

+ * + * @return a {@link String} like /element/element/element. + */ + 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(); + } + + /** + *

Pop the last element name from the pseudo-XPath lookup stack.

+ * + * @return a {@link String} like /element/element/element. + */ + 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) + "\""); + } + + /** + *

Handle the start-of-element SAX event.

+ */ + 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(); + } + } + + /** + *

Handle the end-of-element SAX event.

+ */ + 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(); + } + } + } + } + + /** + *

Handle SAX characters notification.

+ */ + public void characters(char buffer[], int offset, int length) { + if (this.buff != null) this.buff.append(buffer, offset, length); + } + + /** + *

Reset the current characters buffer and return it as a + * {@link String}.

+ */ + 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; + } + } + + /** + *

A simple class holding the core resource properties.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

A command-line interface to a WebDAV repository.

+ * + *

When invoked from the command line, this class requires one only + * argument, the URL location of the WebDAV repository to connect to.

+ * + *

After connection this method will interact with the user using an + * extremely simple console-based interface.

+ */ + 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(); + } + + /** + *

Parse a line entered by the user returning a three-tokens argument + * list (command, argument 1, argument 2)

+ */ + 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; + } + + /** + *

Pseudo-nicely display a list of the children of a collection

+ */ + 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)); + } + } + + /**

Format a number aligning it to the right of a string.

*/ + 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; + } + + /**

Format a string into an exact number of characters.

*/ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html new file mode 100644 index 000000000..9ca0cb4fa --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/http/package.html @@ -0,0 +1,12 @@ + + + HTTP Utilities + + +

+ This package contains a number of utility classes to access + HTTP and + WebDAV servers. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java new file mode 100644 index 000000000..24964795c --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Location.java @@ -0,0 +1,805 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

An utility class representing an HTTP-like URL.

+ * + *

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).

+ * + *

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:

+ * + *
+ * + * URL components + * + *
+ * + *

One important difference between this implementation and the description + * of URLs and + * URIs is that parameter + * paths are represented only at the end of the entire path structure + * 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.

+ * + * @author Pier Fumagalli + */ +public class Location implements Encodable { + + /**

A {@link Map} of schemes and their default port number.

*/ + 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)); + } + + /**

The {@link List} of schemes of this {@link Location}.

*/ + private final Schemes schemes; + /**

The {@link Authority} of this {@link Location}.

*/ + private final Authority authority; + /**

The {@link Path} of this {@link Location}.

*/ + private final Path path; + /**

The {@link Parameters} of this {@link Location}.

*/ + private final Parameters parameters; + /**

The fragment part of this {@link Location}.

*/ + private final String fragment; + /**

The string representation of this {@link Location}.

*/ + private final String string; + + /** + *

Create a new {@link Location} instance.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return an unmodifiable {@link Schemes list of all schemes} for this + * {@link Location} instance or null.

+ */ + public Schemes getSchemes() { + return this.schemes; + } + + /** + *

Return the {@link Location.Authority Authority} part for this + * {@link Location} or null.

+ */ + public Authority getAuthority() { + return this.authority; + } + + /** + *

Return the non-null {@link Path Path} structure + * associated with this {@link Location} instance.

+ */ + public Path getPath() { + return this.path; + } + + /** + *

Return an unmodifiable {@link Parameters list of all parameters} + * parsed from this {@link Location}'s query string or null.

+ */ + public Parameters getParameters() { + return this.parameters; + } + + /** + *

Return the fragment of this {@link Location} unencoded.

+ */ + public String getFragment() { + return this.fragment; + } + + /* ====================================================================== */ + /* OBJECT METHODS */ + /* ====================================================================== */ + + /** + *

Check if the specified {@link Object} is equal to this instance.

+ * + *

The specified {@link Object} must be a non-null + * {@link Location} instance whose {@link #toString() string value} equals + * this one's.

+ */ + public boolean equals(Object object) { + if ((object != null) && (object instanceof Location)) { + return this.string.equals(((Location)object).string); + } else { + return false; + } + } + + /** + *

Return the hash code value for this {@link Location} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Return the {@link String} representation of this {@link Location} + * instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the {@link String} representation of this {@link Location} + * instance using the specified character encoding.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Checks whether this {@link Location} is absolute or not.

+ * + *

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.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return the port number associated with the specified schemes.

+ */ + 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(); + } + + /** + *

Parse scheme://authority/path?query#fragment.

+ * + * @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 }; + } + + /** + *

Parse scheme:scheme:scheme....

+ */ + 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"); + } + + /** + *

Parse username:password@hostname:port.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

The {@link Location.Schemes Schemes} class represents an unmodifiable + * ordered collection of {@link String} schemes for a {@link Location}.

+ * + * @author Pier Fumagalli + */ + public static class Schemes extends AbstractList implements Encodable { + /**

All the {@link String} schemes in order.

*/ + private final String schemes[]; + /**

The {@link String} representation of this instance.

*/ + private final String string; + + /** + *

Create a new {@link Schemes} instance.

+ */ + private Schemes(List schemes) { + final int size = schemes.size(); + this.schemes = (String []) schemes.toArray(new String[size]); + this.string = EncodingTools.toString(this); + } + + /** + *

Return the {@link String} scheme at the specified index.

+ */ + public Object get(int index) { + return this.schemes[index]; + } + + /** + *

Return the number of {@link String} schemes contained by this + * {@link Location.Schemes Schemes} instance.

+ */ + public int size() { + return this.schemes.length; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Location.Schemes Schemes} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Location.Schemes Schemes} instance using the specified + * character encoding.

+ */ + 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); + } + + /** + *

Return the hash code value for this + * {@link Location.Schemes Schemes} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Location.Schemes Schemes} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, it is a {@link Location.Schemes Schemes} + * instance, and its {@link #toString() string representation} equals + * this one's.

+ */ + public boolean equals(Object object) { + if ((object != null) && (object instanceof Schemes)) { + return this.string.equals(((Schemes) object).string); + } else { + return false; + } + } + } + + /* ====================================================================== */ + + /** + *

The {@link Location.Authority Authority} class represents the autority + * and user information for a {@link Location}.

+ * + * @author Pier Fumagalli + */ + public static class Authority implements Encodable { + /**

The username of this instance (decoded).

*/ + private final String username; + /**

The password of this instance (decoded).

*/ + private final String password; + /**

The host name of this instance (decoded).

*/ + private final String host; + /**

The port number of this instance.

*/ + private final int port; + /**

The encoded host and port representation.

*/ + private final String hostinfo; + /**

The encoded string representation of this instance.

*/ + private final String string; + + /** + *

Create a new {@link Location.Authority Authority} instance.

+ */ + 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); + } + } + + /** + *

Returns the decoded user name.

+ */ + public String getUsername() { + return this.username; + } + + /** + *

Returns the decoded password.

+ */ + public String getPassword() { + return this.password; + } + + /** + *

Returns the "user info" field.

+ * + *

This method will concatenate the username and password using the + * colon character and return a non-null {@link String} only if + * both of them are non-null.

+ */ + public String getUserInfo() { + if ((this.username == null) || (this.password == null)) return null; + return this.username + ':' + this.password; + } + + /** + *

Returns the decoded host name.

+ */ + public String getHost() { + return this.host; + } + + /** + *

Returns the port number.

+ */ + public int getPort() { + return this.port; + } + + /** + *

Returns the host info part of the + * {@link Location.Authority Authority}.

+ * + *

This is the encoded representation of the + * {@link #getUsername() user name} optionally follwed by the colon (:) + * character and the encoded {@link #getPassword() password}.

+ */ + public String getHostInfo() { + return this.hostinfo; + } + + /** + *

Returns the host info part of the + * {@link Location.Authority Authority} using the specified character + * encoding.

+ * + *

This is the encoded representation of the + * {@link #getUsername() user name} optionally follwed by the colon (:) + * character and the encoded {@link #getPassword() password}.

+ */ + 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(); + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Location.Authority Authority} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Location.Authority Authority} instance using the specified + * character encoding.

+ */ + 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(); + } + + /** + *

Return the hash code value for this + * {@link Location.Authority Authority} instance.

+ */ + public int hashCode() { + return this.hostinfo.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Location.Authority Authority} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, it is a {@link Location.Authority Authority} + * instance, and its {@link #getHostInfo() host info} equals + * this one's.

+ */ + public boolean equals(Object object) { + if ((object != null) && (object instanceof Authority)) { + return this.hostinfo.equals(((Authority) object).hostinfo); + } else { + return false; + } + } + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java new file mode 100644 index 000000000..3ffa0bac7 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Parameters.java @@ -0,0 +1,474 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

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.

+ * + * @author Pier Fumagalli + */ +public class Parameters extends AbstractList implements Encodable { + + /**

The default delimiter for a {@link Parameters} instance.

*/ + public static final char DEFAULT_DELIMITER = '&'; + + /**

All the {@link Parameter}s in order.

*/ + private final Parameter parameters[]; + /**

The {@link Map} view over all parameters (names are keys).

*/ + private final Map map; + /**

The {@link Set} of all parameter names.

*/ + final Set names; + /**

The character delimiting different parameters.

*/ + private final char delimiter; + /**

The encoded {@link String} representation of this.

*/ + private final String string; + + /** + *

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}.

+ * + * @throws NullPointerExceptoin if the {@link List} was null. + * @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); + } + + /** + *

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.

+ * + * @throws NullPointerExceptoin if the {@link List} was null. + * @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 */ + /* ====================================================================== */ + + /** + *

Utility method to create a new {@link Parameters} instance from a + * {@link List} of {@link Parameters.Parameter Parameter} instances.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified {@link List} was null, 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); + } + + /** + *

Utility method to create a new {@link Parameters} instance from a + * {@link List} of {@link Parameters.Parameter Parameter} instances.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified {@link List} was null, 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); + } + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters} instance using the {@link #DEFAULT_DELIMITER default + * parameter delimiter}.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified string was null, 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); + } + } + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters} instance using the specified character as the + * parameters delimiter.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified string was null, 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); + } + } + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters} instance using the {@link #DEFAULT_DELIMITER default + * parameter delimiter}.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified string was null, 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); + } + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters} instance using the specified character as the + * parameters delimiter.

+ * + * @return a non-null and not empty {@link Parameters} instance or + * null if the specified string was null, 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 */ + /* ====================================================================== */ + + /** + *

Return the number of {@link Parameters.Parameter Parameter}s + * contained by this instance.

+ */ + public int size() { + return this.parameters.length; + } + + /** + *

Return the {@link Parameters.Parameter Parameter} stored by this\ + * instance at the specified index.

+ */ + public Object get(int index) { + return this.parameters[index]; + } + + /** + *

Return an immutable {@link Set} of {@link String}s containing all + * known {@link Parameters.Parameter Parameter} + * {@link Parameters.Parameter#getName() names}.

+ */ + public Set getNames() { + return this.names; + } + + /** + *

Return the first {@link String} value associated with the + * specified parameter name, or null.

+ */ + public String getValue(String name) { + final List values = (List) this.map.get(name); + return values == null ? null : (String) values.get(0); + } + + /** + *

Return an immutable {@link List} of all {@link String} values + * associated with the specified parameter name, or null.

+ */ + public List getValues(String name) { + return (List) this.map.get(name); + } + + /* ====================================================================== */ + /* OBJECT METHODS */ + /* ====================================================================== */ + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Parameters Parameters} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Parameters Parameters} instance using the specified + * character encoding.

+ */ + 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); + } + + /** + *

Return the hash code value of this + * {@link Parameters Parameters} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Parameters Parameters} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, it is a {@link Parameters Parameters} + * instance, and its {@link #toString() string representation} equals + * this one's.

+ */ + public boolean equals(Object object) { + if ((object != null) && (object instanceof Parameters)) { + return this.string.equals(((Parameters) object).string); + } else { + return false; + } + } + + /* ====================================================================== */ + /* PUBLIC INNER CLASSES */ + /* ====================================================================== */ + + /** + *

The {@link Parameters.Parameter Parameter} class represents a single + * parameter either parsed from a query string or a path element.

+ * + * @author Pier Fumagalli + */ + public static class Parameter implements Encodable { + /**

The name of the parameter (decoded).

*/ + private final String name; + /**

The value of the parameter (decoded).

*/ + private final String value; + /**

The encoded {@link String} representation of this.

*/ + private final String string; + + /** + *

Create a new {@link Parameters.Parameter Parameter} given an + * encoded parameter name and value.

+ * + * @throws NullPointerException if the name was null. + * @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 */ + /* ================================================================== */ + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters.Parameter} instance.

+ * + * @return a non-null and not empty {@link Parameters.Parameter} + * instance or null if the specified string was + * null 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); + } + } + + /** + *

Parse the specified parameters {@link String} into a + * {@link Parameters.Parameter} instance.

+ * + * @return a non-null and not empty {@link Parameters.Parameter} + * instance or null if the specified string was + * null 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 */ + /* ================================================================== */ + + /** + *

Return the URL-decoded name of this + * {@link Parameters.Parameter Parameter} instance.

+ */ + public String getName() { + return this.name; + } + + /** + *

Return the URL-decoded value of this + * {@link Parameters.Parameter Parameter} instance.

+ */ + public String getValue() { + return this.value; + } + + /* ================================================================== */ + /* OBJECT METHODS */ + /* ================================================================== */ + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Parameters.Parameter Parameter} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Parameters.Parameter Parameter} instance using the specified + * character encoding.

+ */ + 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); + } + } + + /** + *

Return the hash code value for this + * {@link Parameters.Parameter Parameter} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Parameters.Parameter Parameter} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, it is a {@link Parameters.Parameter Parameter} + * instance, and its {@link #toString() string representation} equals + * this one's.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java new file mode 100644 index 000000000..722a0d46b --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/Path.java @@ -0,0 +1,559 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

The {@link Path Path} class is an ordered collection of + * {@link Path.Element Element} instances representing a path + * structure.

+ * + * @author Pier Fumagalli + */ +public class Path extends AbstractList implements Encodable { + + /**

The array of {@link Path.Element Element}s.

*/ + private final Element paths[]; + /**

The current {@link Parameters} instance or null.

*/ + private final Parameters parameters; + /**

A flag indicating whether this path is absolute or not.

*/ + private final boolean absolute; + /**

A flag indicating if this path is a collection or not.

*/ + private final boolean collection; + /**

The {@link String} representation of this (encoded).

*/ + private final String string; + + /** + *

Create a new {@link Path Path} instance.

+ * + * @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); + } + + /** + *

Create a new {@link Path Path} instance.

+ * + * @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 */ + /* ====================================================================== */ + + /** + *

Parse the specified {@link String} into a {@link Path} structure.

+ */ + 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); + } + } + + /** + *

Parse the specified {@link String} into a {@link Path} structure.

+ */ + 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); + } + + /* ====================================================================== */ + + /** + *

Parse a single path element like path!extra;param.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Resolve the specified {@link Path} against this one.

+ */ + 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); + } + + /** + *

Parse the specified {@link String} into a {@link Path} and resolve it + * against this one.

+ */ + 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); + } + } + + /** + *

Parse the specified {@link String} into a {@link Path} and resolve it + * against this one.

+ * + * @throws NullPointerException if the path {@link String} was null. + */ + 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 */ + /* ====================================================================== */ + + /** + *

Parse the specified {@link String} into a {@link Path} and relativize + * it against this one.

+ */ + 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); + } + } + + /** + *

Parse the specified {@link String} into a {@link Path} and relativize + * it against this one.

+ */ + public Path relativize(String path, String encoding) + throws UnsupportedEncodingException { + if (encoding == null) encoding = DEFAULT_ENCODING; + return this.relativize(parse(path, encoding)); + } + + /** + *

Retrieve the relativization path from this {@link Path} to the + * specified {@link Path}.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return the {@link Path.Element Element} instance at + * the specified index.

+ */ + public Object get(int index) { + return this.paths[index]; + } + + /** + *

Return the number of {@link Path.Element Element} + * instances contained by this instance.

+ */ + public int size() { + return this.paths.length; + } + + /** + *

Checks if this {@link Path Path} instance represents + * an absolute path.

+ */ + public boolean isAbsolute() { + return this.absolute; + } + + /** + *

Checks if this {@link Path Path} instance represents + * a collection.

+ */ + public boolean isCollection() { + return this.collection; + } + + /** + *

Returns the collection of {@link Parameters Parameters} + * contained by this instance or null.

+ */ + public Parameters getParameters() { + return this.parameters; + } + + /* ====================================================================== */ + /* OBJECT METHODS */ + /* ====================================================================== */ + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Path Path} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Path Path} instance using the specified + * character encoding.

+ */ + 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(); + } + + /** + *

Return the hash code value of this + * {@link Path Path} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Path Path} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, is a {@link Path Path} + * instance and its {@link #toString() string representation} equals + * this one's.

+ */ + public boolean equals(Object object) { + if ((object != null) && (object instanceof Path)) { + return this.string.equals(((Path) object).string); + } + return false; + } + + /* ====================================================================== */ + /* PUBLIC INNER CLASSES */ + /* ====================================================================== */ + + /** + *

The {@link Path.Element Element} class represents a path + * element within the {@link Path Path} structure.

+ * + * @author Pier Fumagalli + */ + public static class Element implements Encodable { + + /**

The name of this path element (decoded).

*/ + private final String name; + /**

The extra path information of this path element (decoded).

*/ + private final String extra; + /**

The {@link String} representation of this (encoded).

*/ + private final String string; + + /** + *

Create a new {@link Path.Element Element} instance given its + * url-decoded components name and extra.

+ * + * @throws NullPointerException if the specified name was null. + */ + 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 */ + /* ================================================================== */ + + /** + *

Return the url-decoded {@link String} name of this + * {@link Path.Element Element}.

+ */ + public String getName() { + return this.name; + } + + /** + *

Return the url-decoded {@link String} extra path of this + * {@link Path.Element Element}.

+ */ + public String getExtra() { + return this.extra; + } + + /* ================================================================== */ + /* OBJECT METHODS */ + /* ================================================================== */ + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Path.Element Element} instance.

+ */ + public String toString() { + return this.string; + } + + /** + *

Return the URL-encoded {@link String} representation of this + * {@link Path.Element Element} instance using the specified + * character encoding.

+ */ + 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(); + } + + /** + *

Return the hash code value of this + * {@link Path.Element Element} instance.

+ */ + public int hashCode() { + return this.string.hashCode(); + } + + /** + *

Check if the specified {@link Object} is equal to this + * {@link Path.Element Element} instance.

+ * + *

The specified {@link Object} is considered equal to this one if + * it is non-null, is a {@link Path.Element Element} + * instance and its {@link #toString() string representation} equals + * this one's.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html new file mode 100644 index 000000000..155907e08 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/package.html @@ -0,0 +1,29 @@ + + + Location Utilities + + +

+ This package contains a number of utility classes to parse and + work with URLs. +

+

+ 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 must be registered with the + {java.net.URLStreamHandler} class before they can be used), prompted + the re-development of a similar API. +

+

+ 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: +

+
+ + URL components + +
+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c87bd9c64e31451a1be5ef7b6207d817161344d GIT binary patch literal 18130 zcmWh!gA~n6j4nr~FuFS>bc8fYDk|begMcGNL>+>Jh@=WAIzU27T2%Bm zLPar<5)j?%d;Wp*oagzTPo86CW2vhb;0@7$Ib)!`t(BTz;NswPczF2YLwDTim=BK{ z)KpbxKMy~9I6jnmyRE)IaejNHY2w!M{^$OMkE@^FzIomG_D(OIzA?E{mfQ6H$IP8; zT`jLG@~%^!fBjwhlrlXr8$k-8BwoXEa-Hz<@dSrHn;fx z?T0DR*u=?G&45T>Sdo#!Gnq`ueK-gJ#(*;LZ~gyF0Fp_+6=SU|d}X*M?nRXTt;zZX z+a{mpg?nd4zny=^Xi2hHz5a5oimxOfn@kEj=SM29}k3 zIWNE93e^~k5R=s_EVl$yuiY?%f!LzTn|EqW0V7o%;A$OJ7S~*;jKUk^#o8ZJafl~{ zvfcDNW&KM0v*)835IC0KH<>j!JTkM8EUT+*n)5n!nvn|AeFVN*`4X=O;npzgm+<S4sb|9ixr+WHCH1-J|NdqK zOhM*Zjo$4M??xUpzG^1*=ZdlMXBkVW#!$USA2z*x6tgmN!|u`jcaM{{zPua#rCJbb zA+;7}(%G`qpN&;IKGuntAEe^#EbqbUW{)WAKMA9EwSRg^GrKJA(B1KQrrz!>KnhN0 zhm-&jAM`Nh$g$d&g9DZGD=ow!7un)4j94&~6o~ zO5gmTdQGQJ-0A6){jD0d7S-kDosC}`E2FoZo~8U)9?DvDe){AYlL;W@8Duzr7=wc5 zOfq46fAJB54VRtuKAqRodnux+EA+oi(KBnzrL5kiP&n#PCTuxJzPA20`JLY00Kn$G z@EWE$5>_3iX+&6p_AuPN>~OfkD%G4nJeZ26TM~<`v_G#FJ6nba7bz(#d?@t}(F-p1 z5ze8oTCNiMa)ZmR1wx}$;j;0)Vj83=f8~8u1W!Pl%zKq zd^mgji%JPPeFFsesf1IH{;L&XN^|OaH>Q>5KeS0lvq_VF^z40XJdJ{+$!5=>yt_Z> zaDk|e-d$u25ke?0D)%9e&_PX@rV_a$&a^QKp>IeuL5W2wZw?O@5Y1rHTejdJ49D0U zB@8KVjj6(DFpdE+A~bIJ>+9A_;;0{)tC=MR08HeXwx^ePR6tHUb+Ps*W-&l)FF4r( zaA^8X{+J_W`ENid<+c9Q`rI2oI3^b+K{#rG8aeXRm3U={b*AajFs~m2#wqr-r;S_1 zZoC}Q2&VZl4mHJV*a1*h_SHx749 zu3iTp1Cs>G&O#PfJQ_Q^p#w3D9lPhGI)429y)kselnn(3O#f_tYG(dD+%oQ9F;gNk z_pXxPa>F1T8DNu*A?xOVh`!4-xSVw_>v392xi1l+Gu+F0ek1K-;|V|$-N(^32Fcr3 zv3d>n@!yVtB%Mt@3Z8`o-fU#*j#Y7bS*Pxz0T8wKo-@n(xm3|hNS6j4-UC1lx* z9sf=`>MRI6Bhtr0^tY}}rptQXwi%Bwas#BwajXy-7I}U~(QCg}3xRti1rULJyE^?> zFDA~^7Q?@!*BN}WaT?&GW$cXA8GLxHoAPDr76cGE+>%l+9v0s`-2PdlZ&I_%5^Ci0!xUg3AV^@`VG{Bi>_cs5D&B10 zA<+4FaW&+d2itW-j=RoTwzIdP(leMm9BX>OY_0xWJ&yPhV@XUE^4_`YJ$~2o(a7BW z^K*?N!}COynADhPMq`*mqtB~1{jEh>Fc1GmzfZXyLaSD29XbWIJM!x3!A>)o;~oio z3vni_;-xTjlECP~Ad(-&d1z)Q7Be&AGvCHZrI_8K&j=*#w$nJ8!mRAxOjq!C;K~73 z9Nh=4JO``GZ;EodEV(;Ogan71PMsZn`~G0}5uHOdCUla575vr7kM3npA6@*Y|Eh~8 zY&|xAU3k&xSNFnr6Z~sL`k#_7j~N`zNkew;Hw#|DaMn!9`B!#QT;;!dHu8TY`37gd zIKKOIFQWk#r-(q0?>+L4&II?|1l>UQj4=5+&^h3Uzf4LZ+*^^RJw2*!&09aX6Dv_8XC+7F~21+&EiG6X1V6^tFeo_DBH%P+tj|$U2}_ z@gmmVS<|6}4&tL|JZK$FS`@8tuT64m(ElzAQ(qun#FbLck3QC}iRVjP92Mmv3+wZ3 zoV^GCTX{W97}CQ~5~25^sm!-}-=mWf%yCH0e>w%bRw!UE7AYu7LK%{XO;+NO;$%_e zgQpL8Kbi}JYcw9Zb}x885Slh57rYR6Kse3=qUA)VkLT#x!a!=p%j}aaT@DkbF=Xr5 zY=B3Xh)y7;;=T<+VSX!sGMOo|uto%!cX_ffI9S6BVEw!yXgb;q5Sk<+Wsd*8klUXi zoKFATE=jlLGoo>pd{d0rUm}`Sw)FRu1_4LxoYpw?@Jm)z5Cr2|$phn7@T7 zdH3Q=NG^jdbdl*9XM6U%5TZ!ziMhh z&H#=Waf|>kg@H(65@bY&$vRW+>csI4!%+0|qH7VtF;;ef^&oUcEC!^Qu}Me4wUz3c*SfG`P@Lg$nv<&>`h_l6wGy;I#L z4ThwEOo0pMHRVFv9iSLPD^glwkh=UzyrHhT(HiITv{X}Fm8_t2JQ4UDpUM3A=}g6z zhEo{)O4lgO^yAjimuZMqz zA&kJVhM`)3%sqY9M}+m^Y=1z9I*;JZ&Oy*&MP9HQoma|-VMR&6jj&W(XGnZoz{bNA z{TlS20d*NBR&+=#SX~@;8GX#;?FOI;K-dIR?F4A?xw(ZoU_^shVYYf5uBQe}(L|Uk zwFvjR5P^Y+(l7(_Xx&vvm2mXG-6ECQ4AmS{M_!B8jZ|5J$Djct;arnDhHjDQnwSp9rAUGhEx}tBu}NH$H?Njmc4A1R0;N zX&(d*Wh&oXg{f?~JM#gN#~^jqI~Ep>&TAKpfvcXj?!tN|qBy{j&*NAH+Fc7KLamMH zu8n$A8*yN!><*}tYft&qo$0RK?n=?aKxg07Md-t%F~F%rh_G6hIa13%DD09{n{~b} zX2dN1O?@a|{mBEEHqEO*%ljkkwnT4uZfS!U0Id%}x62zYdn8pJaEAxn6{o>4)GP!s z%3r^+!?yP0s;fzDjaftvvr{-zfyJ=;QyNFtpTd ziXhy7HQ?_P3ezbI`fm=x$9RkPhO2A@iUZYAt2N&dElYDCf_hiHm5U$?G~Z1`&;Vph ztLWQS@!zdDkv1uVwg(n)mC{D`Nyx1GxG)t`A-Cyi0eL#@ng;F4Wa4w5dqb?C=EHjx zCQS`@lJe?qv3TBNr5@L;y{&@t6=OiAkCR};X0ea}8ZYR1*>ry-U^OB@^FsUQHvx(v zpy!1LIa~Lc3jBc@c7Z-30q#lvBBFQ0?Njm zdfphO;S5x^B=b6=B(*H{_#0 z7yWwy)TV>l8w37^V3{oRd(`LP;eY^uB`pqUlfqemZXHZ-oZ*n@sJfW-F#}>`{XEQc z3MS?aGuVjedfb0^U0a*nt!W5elE+I2;62$u?8bG44M>*}^+6JXuXF3*AYa+&eG%Y> z+Cyq3ojXP%?(Eq}D?o>Ct~FRAVwnO0U}My1T%8Do?Iv2@4xE+W)-mfJVGo15vEHLVv6k+_tm?&ZyM3D=s z10N_5Kr9J{#S9@y5KBX-bGp@Gn)f8;@^yVy$n?PUt8wH>PK1`(}(?CUr=2p7|*!qh-)0b5?P4 z!I_{YDU+M5*GFRGrpY!G$zJk5lKM_!w@s801)F2X)wiExb9U7xJ-`xX-WM-qI4n@U zz4}|IxNbO3U_G94g7EKWk#TIZ;#Rk zuSdgDl;74{zU8EWS~M7^^IJ~(pugWUEQvpBfHmajBpTsFGxOXu1NAq|5Li*LQ1C=UGlQ?#xt8bPFW@RG~Cb zqlFE?I0Cvkb9xG(;Qbdk0(9D4K^3$7c5wN+`caL)(WBYRSA!X?Sn$LXX}xe6+3)JX z4;8GFVwD{MK_bBM6tbCS$o}PiI}TbOT^6`0t@K^}Boa^;*UH?K$vXO6TXp&RGLQ3L zbN9>1=^4rcnFbQ&Gt6Q+WGZ(&GDH(7CWc>%Gm?g74f{S%D)HAd*I?VgbZO0MT_@ zJiYb)w=ZHBV(;~|GTX)1#oqEXzjwIqJd|Dyf^=9Wm-hofa;?DXNDmXjjCeQ4&yX(UP>t9_gpDDqKAjH-K%<6HhofI4B9RI{N?1lEEz)jx0NS8vI+ zcU^5$KzCXVq>Ps24a!y8HLDK;O7)oJ0h$#)uWw)ixe;QQb}HZ-7p zznQ;LJi^5x{~qONR|eQ= z0}#$li(g73Aw|AACXW)EF9UYRA)nzG)g=}<%8z$dhqE__-3aJZBA)gI)wF{d^4a*b zPMCTyRX%LV&NKm@yq3Tnx5y%-xs4W-%mnfifuVf^rX-E*BKFgASe^u~vIwAL;cJ95 z%0LSC$^G?gGQYtYebX-Mx-G(Lhk!X1 zaF(}6Va=`0QKG9Dc7z8H`njn-Qx`0(fIRaoQ0|txbgA$!t|O(K_A$z2GIRmRIAXhp z$M84k(O|)4hf3E3t!?w$#3$2BJ`*hp7$mSSv*6SBUB)CU&MRW@8SRqVLlBxiq&NiTqB~?liUSdg>;l~2HRTEfi zLCM=J;)x~>a7Jzko3K5-j{ICVtO}S%!j_uhVVK_50Uq7hdmgfa+GWEj+>&H|Z>*%X zD4bA`2Ki)Hcfpv0)nEm}wI1i!7v$A5q3sm=9FZ!o#5|CasEs~2Cb4yqP%N;m_pO)v-7)FQX*;OcP z(t-&L9N*wlh7#*hKzpsW9c;=zyZ0t4GCQVEuJ+^Y2*8hQ_l3nWJO0ZB1Kgk#Z{S}k zKUUqu07Hv%<*tZ^w-+9#N5IvVKs|R=ljFj0bWJ+ko+fz$JAqv;k-EYQAh1N|YZih0 z^7Je~^Xl|U%gb8Q&oE4^{YllC_ab9X-waK}_@*u0#N1yWhTUvnLmLeJ^aFtL?)lH{ zwZ_cRQ=t-~md;B!zA@pNn=Skwh9F)4q`703rFFB@P_2$+TSFp_^7ZSB{ahrUlzPr= zYJ!y(oX6cc;cNSYPvT&OJK7`@JarNjUb96W3g*7MUoA6~KtHBR%mr-YvcC4RrcB)x zhV`FOnGaJ!j2)wU>0|T-jhR~EXHJXAOXF+Cgq^(FK7M8!v1?ma~V?#I>il5#PlazpYc77 zY}MY9?kCjqf~=WDcDI9CzG`c!*FMDq9|m? z99wENk3uR8*bAP6)A+rQrXrrhAX z2Q$PVa@JiLZj~>j-n)*p)ccim#}U@ndz zJt4|C{#KvdnR{12pa#Pa4CfU@WZMeV&;ooq0*(@iY`Pa2s?UB(a_BNdxMRpj1R01P z;WOj;sf1AQR&spNXMF)(d%wa4Bw7My1BbQZc)H2!yJv=A3$z%C2xWQO$xe|&6}4vx zCd-TRs3C77K}* zoD|%ZjI$bKbtP*ceR_B$f}kZCuT!c z$($GCX2sFo?Hi`@LJZ5Z2C@F{QlcL7FQuwqfioerbUQ-V8eo-&r8+LQ!3+8N#>hfc zSycd~JL%H%#S|vICWXk=CvAB1LA;6X=!ynbGc*3wzzh;xnOw%Yc+ETz@v>!!m&_rx zu?((Odmq;W(5CdKo0S?>H*g?}rRPmPRCBB(iSIW-5jc^EmlDIIvW$SKPK7kG8O!T^ zcM=+pqI=^7={lX2lSrMRTiScGb-7XK_sL&jkp*giH{o(>Sf`^b2bo0~Bac1ga$E|2 zztFeaAlZ7QYP$%=iT+j)h;SMR{_V~7g;g3|-f#GwkHUEhb6I4KtQGL>NGss*kauGQ zo;aSj!tU~gmk7kgtA9;LOnN?KLZoT;nuSOMpa{aa0Xi{DH4^L<&RidkJ2f>Pb*y>= zpDp#7eu4i{^+|^N(fnGfZd9GqZ5+dD3BtfP@}gwHV5i!-E*>b%Ox`Lv+1pPc^Cbc$D~0#c0D7 z7FzYYy`$jOS6Yxxd1r%%co6cqASy|AyUsZ|6>aG6v%2T@JBogqakR1un@z5mNKR~c zx#AXhc4O3XLMF;s%J!r=kB9WEw}!DiLZL|J@ahOlEkI`A>6^V@zUW> ziA%rj{>%7{>isKOunLa>Sg6-zqnY+=NFN-4qyb{iKszVs&jA0W6YDSk0yM}4mzi#J zFfs^7k`V;v6@)ov%P0fxumL~fe2|MkxDgQk8we*F{1^a`gPCzla3mcb9s{={Ah;*f zrO5CgXJqIG>I4;yCmuXEJx~T%Ufx6H#~@KS=vVV#(FXhk0sSNJqvtrAoGQE5H@2Y- zHn|DbUoY7mRM-bv?#tfS!BFQ5zORi@IYPg4_Byk7P5yc^$?+|QLz0B}t@0b)ieY0% zL{as&hcQB}+;6#zOK?o-!oOwNEQygkTg^OQn_&jdSgqI`Su#Si1v)yFp8gqW&OlmD zfju;=QUK)j6Kt-=UX_O_Z6NKZ{@#^==d=i1{3&oDc2Kd9WrKgCc@E%2Z?fNMR1L2JdB9Q+myc)3!EY<;AskJZE^-Y@@cO4qj69i zO{r&7saIY3SsR}L5A=Lf`Ei{5B_5U6o62@=_-L9+ZyWMr9R4*EE$c!?AOV@5ZL*ST z#=GhSL_7hZfx26TN8sdG;?-bVs3+D6l3No*9ID<`6va@}o6bm~D-^~7sWo^Ln!KLd z5lOm~?ljzotX}vN^&HalXS@^C&_)#_IE(SVWQCI&Izs;?r74FsC~VW$+thDTLy>Fn zzI0@_n_=sWWL^A`W4Pch5n1{%vpa zEHT%ZwfIwfBG6sOnAalG-TWx8J}`U7npcl6-Sph3o?c%mEWn1^R zlKJTlo9o+0S~cyMo9f4k5FPGz)#~b!GYHX5yH-v6rD^*)O_sl{csG*6__jKB;=d7I zb$o8BTbp3T)R4<49?)qJ=Le!&X5*6iJy%0@Ni>gzpFl zN4Z5Yoc<}gQNz#9d*Mk=%6!Ki)*BA*G^i+moF`~uiRJDP!Vnph5ne@3JW1{)g9PR@NkRobm zbjYs`VbmZDf1T+#~Pm=PW-5X}m(OtYO%)PnBQT~0p&Mid;2NGxhuX1}o~ zj@aS%X;Fzpls>=ZQo=M&4oIP2x8;Xe=}sDF;|)mD=dGSS^>n|J7`vb7pb!%HB>R-a zJR8Bvy+^>hPat3PA?&WRZ?=v%kpP|fcBWY;$F$uW1*o>o!?!iD((3qLGQy9f_$f6f znI7gqA0;w%B<#__gDYGqBq-_^o2c7`9!KwEB+b>bFnb)YSlE%qrFZrbgJEQmOGLW_ zHt7_4z-gRm&%G!lo>v9R>iUUHCYs~P$iMTRgL#=bn~>&N;N+K>o(Z?3Dou`SHk7Mu5#b3QVA&>ipo|zu1V|EvfjYwuGn8t zyNirvI0!(=7gw|bmQZvYqGAO_e|a%Y3uXEtqm-6WL<>QY$_D1cjwl(g$;f4$$?9h= z#AmH){WpFtOONI6kuw*xa#bDxtX&}jNk%{C!mG}`NddT8uI7!;=Zz=4Dk3VRkoceI z7CP;7y6?xA5S9A`QtyDmdK{G>#BSntcQaiKCjO~n0w?8=`y8f4-?_ssl- z>dOu2y>|U_Vyw(pzzCNzUZRa4!yQV^^O2sqrVrlaYd+8dSPk5&7f^w;T8ps~cCjzr$?6H+d%|^Ujr4)CpRA*JTf* z+hsiuv(RDj5lk-JCN<4P4P3i0u30WNgR(sXHx;(&eH5FT9-uK>^V&Ur)XT?(`Ks&{ zFBHZg#lufGzX^(EWc@gE&yHVTP}^#Arp_P_)S0n-RH0?o?OnK?itlP2*EC-5l-JM> zjXaYjS418%;LKX}9K2{9a+P)K_%y<7+Wv>oxw(POP-c60`mm)6!5XAB%p}Sw8 zyHGFog)Ym#ByGaL4jfjFp#>xC*=whzDumzyQtSX-Mqso#zszenE)ZbSaF@>Zq&>Kj zvCBnW&g+%W6ibkFEDMm2$LMypHV??SJWto7C60!jY;$88T4xV*wr}fCt9Rh5)BSPAt+8Dv&O7Gu zi}(YlC27;exOGA8xznQ6=d@55$H}GHmcf1}EuItM7xOu8z|5I*$cMJRt+B82bgCVO zHSR31jcrc%3rssCOqVkB>pi5-_Pl&pS)t`JJ7ftv{ZU$&n6%vrPZ-ompihvFuw9L+I(!PDfhEn`06a zOaHoAVJ|lrV@%;@LeC#N2k+$6%4Lg`)#$QShnt>>`>Q#YAAa*G97{RbGr#I@bUO6! zhv>f_mpWKK(^eejJc(q+2anxO#&SI0U`GOwia<%FcyU}&Dy1)6eed30b>V^ZhuFD= zLb}AC*pwH=ns{8(Ty!(%g#P?5(PK@uK+$nGP@ak@XKKpr}XqVG0fS4j* z59{2!&&3R+X<=CIsUsPVEEOq=uYWp7($>a;MqE4|nEgC6_xS#f8jCC0_ZvNlsc7tf zf3E9|tW(>sSLH`ndOk0lA0+a4e|!Jcp8-?-y{1PF_`_Yy%w0cC+nL!vQFqC1fMH~` z`%YbAXSZlQ)ZW#^I4+X;O&g1Y|L3+Nc*21gxdpD3($=2LuC-RMIX}ys7eq@vlZaH$ zVUMevJ)#rv2lucyhS16_mjwi)!Gu!#?3gg4>X|QEkH4k#uP`_DZXBgikof=1jiCGl zBFK9fz>?RA7EQQQp0{St4i{a7Gfc`wc@NhRaTTA%7Ro0X#8`MI?_`U4M}qxiXXhU8 z_FNMpnb9^WO6&^nGM~a-mtg{d$ z?=2oCtR+19z>8ks6)k7)lD53?c2~`vK7`iPgYxJulU@>5RB4yCbQ`RklbG%cVUYYT z{~sack>WU0xh_JG(i}g&UueSBq{;d_bCSzg6K{bSw%- z&T-Q{$!#W=s5p1m^CbtJGL0hZtyXyVPRW>Zo?!kftVz!yIKP;G^dzCT*J2T^bcrx9 zy2ASBh9j{+R*pSHOjGn%_U?I~J;A;<`M`oK@dbA; z`nUG?#qP6P$OR=DY~3ZvzS3K0aJa)T0L@@L9|PbJSpNw5ea}p%YIb}(rdaWK^No^NBWEl*ET!Ipqu|OlvbYkMpkpuoeDKH zfaCmkUD(w01P#pFy2L(baP4(1nX)xU2UsekzE)2y3V(-w7az_7k~!PIY{8m7@qKO- zig-g3(E^^l$UK60aE6)o)I5Q4*LF=SAj;>}@&k)@>~A|*dq}WIQ^~B6dT{ZTGs|z> zkyqo%Eyvml1^Eos;T2r}WwM=mq~o=S>iIM9Fsj{1wc`c)yXU6*Q1)kObdR0qOGeU}UlIB!Zb@TdBB9B~% zsD?0IqL-Hwv8eQWdaWgAaGGZbFuDBYM>mEz(jm0%KuzN^VrQ84MV23(LCf|#95ZOLVm(RZp&!Hs#?W-+&*Yr9prnAoAlupy^cw^& z9aQIV9%MaWB3jrG^#v4kKjf>xB8Ch!58iv2sh>znu-|ZixLSz0Vl8>0yk0C$f|O3_ zSYS#4z`oBa6Msm!a^5g&K8|)*&z!^xdkpd-Tdq0o6|&mW^CcTGdD3IgFO;hF!`+`V z1vOU5EL3S|?0E6O-R-F>@q%P)^Up^eIV7P7{B2?F7q(I*h-8Nr6FHU8VJR6&QaG9m zCCj4ija)(cC*7IXe*tmIvlQj70(gmiYq$3_^(@U*i7WM^`cKrap0@72rHiYwmx_~Z z7X6p23$X{5-C-GHN1#%o=w*$fzk;2w zKA*^B%taSUzjWtbqb5MV97>|F{Cj?F^Kb(v!bZ!et(q}BKEqKEHg96|;$;o9Tl21K z#+Yi#s*~PYIOk=XG|PnzAvCcxTxXBWrJDiM^vS5_!@dxYC)VjJe}{c1R9S!Onrm3r zYW93R0Q*ExO@Xg?oXrPJrRb?Xegs~<=sdQAjUrWdSN+^zBu1sKw%%pQDn|FKuf1Ew z)jWU=pqoipMj}*+D8Li{6(&bW<=&>i@Ef&kBMhH~>EMSnA&MwAAk#fzfLrJCs_U-K z+X*nyRRW+yE56NS=>!ZU>^?*`At{5qC;{$z)gZCUK0qE$5R=p-$sPF?*k0;9cA#(}#+RB^2hAF72Pw@tF7ExFgg{9Fe_Yx97q{9mbqifO}^gCE|#y zy(QO3b|bJEdV`%C3T@;pHvZHkts?+&-kYv0GTCelj~!Y8h)d3XfslhxnPL|L@O~lP5LV8j^&@Tk=A7i%*rEe0P!5^(wcH&qRjAU! zScl@qgsr`T+4*ix1MI~NtFqoFADoFV2B1lh?|2{#ec5|(KPEOO>~{Wt+d7lLyNpp+ z!7P0Wg9U}@i^!&U-I>gC{4D&VNI>*6_~d6O%YpevW;k6of%!T zICTmX#lU9jb4CM#%t)a-Xw+D#ru@fQ7JsE~*c3AgktRD=)5za0OP+4OogLAXC!QXczPZPy-ie{!u5j#9{kp&bjm zBSLHM&s!xTzc`7~2e16Y`P^6u#aG)wU5PMV z(TFJ(eWf9Rqthl2-K^3(YsOah=~y;P!sYvFhp*h=6a(A)b_{}vd&egCfIcUp*7zti z69ZQFefjZ7DZe>!Qh;}Zg+=6cqe5KC4pXRPMp4pPG5tL)p7`#ml5^Te*udMbw{FwY zeu~Q>a7km&d_Hj6W-RMjJfSF8U|RdWMxv{C3-FZ$*DNc?#}9)hCkLk)~U+%tRFi$`0Z)SHRT-1 zMEaLs^Liy6_OlO z4ziH3(^m_U6U1{<{h0E>E0{bYn|BbO;fmI^G*J@|ZS84C1w7wzd;*=)Yay7axuQL? zqHQ~TG^ARsG^k8^?x=pX@xh9&I9`n>_*u01o{YPo$>4$coF(t7nW?l@{*0vqUS3k# z(jnM9V%0J(*y{YMRjTwphMU{J=GluWYttqF0O_%s>f^^uv(kg@KSKUtmBwMgtZ)`+ zZrOSXiAwUZIGG-$DNHwb5ayjJa@ze3!! zg(nl)?1^Afxv298pmc@Tg`#inDhyxM4-7hX>W=nzTkd_Lm<_vwP!PSPNUyBgck_h{V4=6yH{~z2@kdrsq(TnU{Q@34@rmP#b3X zIzX%csyPs$JrMD?WUc9W9!hBt7Z!KE7h!bP8ypHlKS8(ObP1q^D`qT@~^8SnUTmF-UKJo=2 zYXv<0_`P~@v`x^VWKLA3#=R?=g@ZD2eQPcBc7=?v`Y^dehPG%r5v;fWo~nQ*?+#dH z<}YRxEt2J}JT+gdzNoAxV20dtcMr*f16&-7WR9<+IrQ+1`W&uK`;bA<=n49Ka>$Jm zp`||!2H6hlUmJ|JKf@j0-#~v6(3H-Rv=}xgwuO`L1GcwU8?9nUgtPSo{TCQmhzd zX6v)HNy$5GAG_*~vcbUo_9WDaa>4a4B)(r2tMUHk%o@}A@;F%u(-%GrI6pcFP8@6O z6!z_U8rbZYd1sK)H=y)1CGxSg4Yzny=O%ksS!!3r<)_P_4-eQUoc@FKw}XZsstEPBn~rYFfX4D^Z%Q&_TXp7=pnzx2?_@=DIJrQ0W#Bl@9K z%aw}UCv&E5`hpdUCS0{#vGm!AYu}e^zi+-%{gR0c)Ky)#R0T=~{(UIz{A2M57rm8( zzIhX&M=ctgGX)t-H01(6fUL6=ZT+5@Fb4*xSpw%?u9cXu)~6QvQG}CCe4f5{o%D)Y z>e~-N{!&G#?2I((-Vu?0o?JLHw0I3u&dDDGs9Imyz0;g{@AT;>xxZD>2Sl#`KOu^1 z218RsRGHIsmhHDJ!65;S4DtewY@`nVfN_ELiImT?{Cr6x-@T^8gJwzs{Z}zjmV=PTBhrBS_pT8T{AdnR|amtM6+VW$ zVS0KY=7}a%nWnvqz$xqd0)D4?{EFr=lz{O9ARrARiscK7t-aftwe-b%+Wm-@iH(Z- zLC;_k0rVr0R5<7-YEBA1f60+&m9ApKi_7PJenqT*6B{xRJxl2ge{NHARna*WCEH(v z>yvou68@sLvViEst?2X_P>hH6`#A2NT0rrpC$RoBob5kq|BiX8m$eRpKYRx>5rmuO zeT;#-&wQLX4wCU7R$Ih3rHO2LGvyd1_+?B#vZmnr( zLSAI~*zmI}EEPB08^dcAh_iaGI`l<93u85uu-2GrbK3lqReB8l!8fcyS^_Q6hV9hz z)!U%W8mv?IUmed@vX~t->wt?Pvlxnt%}v}bEExWL_u1Eh%zc1!^1Z+#lgcNs8w)Hq zv*%m(tZyxXG^&Hv@!N)LIak(fU(K@QWhQt!l~QKk%cRhSexU>uvg5v1{nTWn4*L=L zhOTrj-ah*7MzY07vhA&l!kTAouwug2nkMSTd8)Q!a9IxKtK+u^^Uy{ytfZ{bMBtWl zWMWWrNkUP+??o+9L(b>+pfweeLOaUkrE;W;4FHxBQBr({*EwTfG)Fk{XblV>&-SXY ze)-U3js2Q!_P}Ri#}*M>FQ=@VQ?Qspe_gRt)jLV0sWt|?v1ez))V_e(-@hrqg)?o9 zoC{t?V=lM&*D7asd<~W(4(AcL@f14$@X$M-fl-Sgr@OuEbJW}=%*s~`|Mq9D8{h@& zA6WTd!U}?GCtOW_fq?sUslDWvJs>G0K6S4+s?t*GhHLU``h$`ny184>ciw5~>S^g- z4L#W-`$f9PstK`0QR&rDx+Q+vCIk-b7?CIt#ls}GCq1K7S{?VeJlZMNPG^l@&3>6w zu)bHYm2}04*T&^?`~RXN_DMIN_lti9vy=%8gZ^ItlLl=0f54Wj0WwU23E;+f-NFd3PlHB_JW7&ta`knJtD}4HMk(=SvV04MhPskerJZ zh>e*M6cD#NSh}PS%*PNy83>L{O^Wg{1rgDZIgo;PpZTosl7|_=-OyBN&`>vY^qn!n z;!MC|#h8Ob2{V8I;W5J(=ngKr7!63TCR7Ip6u=E+3;(YGMu}80F2aqjHGwwJH@h?g zi?~(cq?iIV16MQyFsM&X0mF2A(+x~}wxmV4X9*Yr0ww6HGz`Y%#6>aR3{wc1_=XFu z6MSTt0s^f$<8fV=#1F1;3%G2|q@BSG=>bciGNI6$PFPx3+^anhjjzbU2!ge{==Bcy zOO#<+s|cH^pnPX+LJFvZ&&{nXdP56jR%LqpbEv#?u|gkUT3$F^$q|y5zLu(Nvy#DcwzF5Iy{GQR_S{i7=O!%6w)d`Rs#vO11xTJulw4K06wmMq|pJY}@ z$Z0Kzx;!w6XT(To+svJei|Qdm7(@wRJWgiPJ^zL{d&Q8&-gg6i%ymE%pJL4C($jjD zKYhB;7<$|tXygGEz=Rx0GTrDL;baW;;SOY_1e9b>T=q<ZN#aoOK8Z%5Xpngx*KX zVC%oW<0c1hl}Ke&Qj1W-T7L8ym<{L)PD*%$Sksu}nuM^?BjcaB*XBzztbb&$P z2w_&`b9&u=$LIBXe&7EG3=|wBEHpd>oc}0@n2IQ{(HkKe2rwBNDF|T9`Fc!qS**&j z{7ESo85jYXU;+EFVf&dFnF8<}=@_}eLc9YIz!3--+XFZN<7n(KVX=h_fK+C7&Vppo z{22{+?FeAa2E)SD9iWVso2`A!DJX!jDIk(egHAJHhA1!rfavxA2N+P`K!ODg9t_Bf zioz)j{W&~X&QCjt5f>tCh*9H4jvYOI1Q}A~NIV5xkVKhM76NqO0E>ghwwFi% zDq&Pr?OL`R1AHLjryMVaOw-I%D*s?YQJYC70Pun1?-MrhHsQk)&;dR`^chv) znhpSszJQ%>L^uWL zkUh(9<&g3b128-7Y5x6Br~ZQl9KbN0S7zdoTHr@rMPE`WzsP86&fO2^Oes4 zzyZA}H|5l)Dpy-V(pJnU0tf3tonl5ZCFBFh0w4vzhDo#Vp&!7~nqbxdYOV1AMn*Vb z3`McbM7sl`0?dSXYVl#eDlCkEnH;c^#fRd0j7pUww=sbRWu5IbTybgol#dZwnBtTl zIv^E!;jAOZj#Ac??-05||3f`iGL;|3a%dBuli zj3Gj45Y{lJxGrMl=VZC(2I&SDVrvzIQ|NVBwF#Vs1@E5*aDm=DMtIT%VsZ$eI2%UT z7;nZMhrGsq1^{4;99#(WM6W+t7?#Y_h?|Za$o>Qs2nv6t^1wHb>nR&Zrn`3AS<2x6 y!fvPHQQm(CKKMc)U`F_PghxJk<(Fr^dFP*pK6>e=r@ngYug5-n?YB2A2mm{WnBJfO literal 0 HcmV?d00001 diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/location/url.pdf new file mode 100644 index 000000000..f970fe310 --- /dev/null +++ b/springy/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 «y¿ƒxÍzv± ,„¶ØRˆP¤ÂKÏ×ïé )Ž¥ f€yÛÕ±-²»ºX]çt±üêow÷WqÙ>ñ+geäÕ«´ãlh»kCŽ7u=öC'†.Þ]–¹2a߄kmøžw}Õ6×ÓT!Ö^ÜU¼3ŠqÏv¬®«KãâýÛG#ûªá}o܍OuµaÖõ—XóP 5Ǫ±«W¬ºœ¢ÀtÆLØÔ¥¦oXεia2iǦ¬š]Ò~¿6LÀµòlLý³zÇû3óþʏ\FÖ*òÖY»÷¼îºvƒ˜Ò¶n»þÚHŸYc¼a;Ì0ã¯ëö›‘Ôlóy±¤h›¦¿Œ›ªdû~u30ì׃¯;֔ü*iër1zϚþêáùÀ¿uÕÀ»…Ç·œ—¼üËüÆ7Þº¨j.öl0|AL|ã¬å&{ì×b8X߀&~χ9Ãó‹é›ÿZfôj\||Çw•”øùÏ¥Tƒ¯\ßóSãð¦–\`ÒÈø–õ ži­p9\r'·©¹Èe–þUÐç W–ø9š½œW£¶á®|ÌÝ]&a+KÒð0k땧ë•ÿ ¤HàßjHR*Ë6ÕgxÞʅ¢~{§­A´tàȏÂUdE‘á…ÇZ™¡Ð²0;*‘­ø·kãmÛpEbÜ ÷Õ¯`K)؎&{jþÝXó„l¤·Hñ¦-yg/EÍ$µ‹mÊ¿Êàu;>àä´õ8ÈÚzêí¸ÿ…=óîø€ÛoÚ÷2Ò+ËAþ‚P*‘F׶ÐC‚}Ře¸¶æR@;.„ƒÉsnî ÁÛ®ÚUÍõ2]R^ÎúŽx÷Žo†ë+̾%ÓxåXzþ v W:  ߎ¥Äýº«Ê£¶ípå¸>b6——2«pñM¿jÏHß0ðF¼)Óv/ëE1ÂihpTêv§ææk9ƒGò‘8ý2¶GÜÕ܈|ºëØWnXvHãÕ3=;LÓtÃ˪®ÍÙf8};TuÉéídFè㼂)&½Q¶©:ˆi[óï”mÔz&o&ç\®$”«¥|±”ÏK+å¾R6Õ¦šméžÐVٶʶ]ض³m«B•é¨LÇ£)¡ãl[²ÝŽw´D€œÓ òM{”·Z좇 Tú4Ö5¨((ðuøD±bùT#K,TðVÒM{x†¸> …«ä{Ö}¦*†ùq«¦J¾¥qNoûšõŸÈ(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Ú¶\]P¸~å͎¶k +ã%a}ÿ¼jë5UßǝFÒ¨fAæýµx›†íyiìôþñ¸$gGáÉ6hƒüDLÓ´ÛtðLІ…fÄf¤fäfaÂݖ 8– x€o@hEؽ‰•™•… ÿÂÞ¶mpmðÀÈŽÄN ÈíÂ.ü;6Á +ÇqÏñˆH ȁB„€à_˜»®ë¾¡1727 +·ðL?‚aïyž^D^ $@êe@î^!òã["|Äÿö⡱Ÿ)ù9PH¨HP`‹ðüÃ\|B +b R 2äA`E‚°ß;@@ð{ñ‰€8L€4̀<,ÂBð…æ E~ϱĐ(€½øÄ@¥@FþAÌI¶$ßÓÄ+ÒSE¸&ۑDûŠdÍo¦¹U¼º’Ó€hBIf®‰Tz’¿Ps—JÞ +͙âË×\'MRA$C¶dÇÓÌD’•T1¢Ù\x’†PS Ò_ÈÔÛ:íH:ÑOd¶sh[&ÙÓ VÉMUbuR™P_çRå1‹ò¨ ± ÉÛ±}y±.Cä9Ž“8³8 œ +±¡VRò“´EIœ$IšdIž©‰Ãb§„éA5ICP§Iš¦Yš§Ÿ…“ád.TèC"aeq–di–eyVä&ɍCàæäaåqžäižåy^qnÅ¡‚/|!®"’°ŒWë…˜˜+ßòÙðGÚÑý{øcšÂ➼ZÿŽEÒñ¿‚'Š”¶žï1—dªd¡bOëÖ0ël ³NkØ$ás">'ãsBÖR&“–¥”Ï‹ùœœÏ ZJš¼Ðt®ËÎoE}NÖç„ i“mÏê^ˆû¼¼Ï<,ȍ¿P¹ù9™Ÿ:9«õsj‡Þ_ÊýœàÉRóîÿ+×ÿ^åšh7u×béÞe‚£áÎðfø3‚¢Ç ‰nt"ÝîLHf¤32|F1ᱎ[ÙáÎðfø3pF¤zªH÷U±î®&¤ٌ|F1á˜! ‘Ÿ>Î w†7Ã×f„3¢ Dvp±îãÒٌ|F1aâì˜! Ñ!Éϒvó„þSØ¿ƒùqäxy¢šSx?Ĭ+²Ø©ÔNþZ‹d!Ësý/ûÕ²#7r¿ þa.¾ ‹õ ,ë¸0ìƒ>,‹…-x¡ÖûÿpdÖ£YEvÏh$íä‘zØÝÅÈ̈Èá¸}Å¡“·‹®>cô¼¸µÿj#]³h ì‡t⠏”<ÌäðcÆph.BÊs7~¹¬²\f•å-«¼e•·¬ò–UÞ²Ê[VyË*oYå7‘UôeVÑcV¹V¯«>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…;± /æ=©±ŽíñöÊ (¹¢/h– +?!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·>ð×5þ_þÿ|̳ôÂC_)Jäÿq¢8'ŽÜûIÙ㛯|ÿ—ŸÞ½ÿùÝûŠ¯?¹<…÷×Ù$üðíwÿþãÓßÿõîçïŸþŠ—¿ýáäêUœ­¥“”ÒžbÚRHkòÉ%›LÒiI +þ(±×%ˆ^„øˆ ‡Zˆ¢†8*ˆ¤Ä֗ šâ ¢>:E5PÖ +;Ci§-Au#Ô7@…=ÔØB–5äYA¦%VÆَï÷s Yאw™—ak‚îGè€xø…/høƒ‚OHì› Æa Fâa(Æ¢a0 +F#±&O„‘wNÀ• ÜiKÍp«É&8W„ƒ8™‡£YX›†Å)XÄ*›`}`…–ha©`•ÒLB'xg„‡x©‡§Zx«†Ç*x­ÔӒ`¾&`ƦlaÎ&­`Ör™T‚yG˜x€™{„ t“Ã/púŽ?!«ìH’ÀŠDà "‚¨0#2LHU;"Ć(±"R8D ƒˆ± j̈il#€d‰ÃWCÛѤµÛÓO P¨ TˆU T ªU$×dá´7se$¹ªCõ¡ +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¸è‹Ì}À"Øv²õTÌ̈́ŠñÄW/Ên”ýèæH͓wäɖŽ­õB+ñ°Ñt(Å=G½Gõ"u¥ç.¥® Î%ö©Ÿw:} =†£š]¿ÅF…H4€B/hîu‹ñ蕀:P÷ìkBGIðª¸×4¾Å‚qæéj~o轡N{HP#‰ê)ÔQã€Õ]Qé 5ß·„Nè …&Ñ8ºE;ù*™D Q£™(zr\b˜ Ì,qLlƒw®äªP}¨ZTq:b>à½#ÊË#ZîU +«àD]OC€Ä"J_JïU£(ÑQ})gQÞ"³œÁhì(“! +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¼)^„&^€òò³–µÇ´•'/=¿¾œ¾‡öåËë©?ôþðö¯O¯§ß~ÿï;žá‹–ûŒ-ö 9ã*P¼t + •È&óº®ëÚ®élWwi »0.‰9f¼,ÅKº˜)^Ò=ÀLñ’îfŠ—t0S¥Ö[áLCMÔBºì…ÀLvÄï’{jÏóínÑâ t§hÏÅÎ\ +‚E{-4¿`õ¸èêvÀØÈ»êL&¡:!ۋƧQMBmBñFÝõÍ-kf¬ÆÛ êŸœ)OH.Šë'¹bV*¡rQà9½ *!3·2 A ˆ0@…2Z Ã¡ ™¬”ÀKÌÔÀMìtø¾0”ƒG¸T‚O8U£2Z-àåPw`&5¸i@ÎvÐk? ÀPŠ +pԀ¤A»´ f z:P€¦’€U¼j`Ö·Øm_‡½/€c ,+àYÓ¸¶Àv |;lcþ+Áu¾kpހ÷–þûØ¡ +‚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ßµ–Ñ›Ï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†§·†,Ç3c +ø·DXVÍáÙ D;áð!‚Uˆ +€Ð\ƒàµyãS¸Ë¾•@4K8V!ª5"ۀyÛ¾E”;üH&–0¶‚Õ5¬o‹=ނ?¹=¤*|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¿`õ¸èêçáõ”óþ< îv£ý–ú£Ä¥J'à>•I¨NÈö¢ñiT“P›P¼Qw}sËrÙ¢â(Gý“3å ÉE^®¤*…`"7«*!3»{ë<¸u¤­óàÖq¶Îƒ[ÇAÚ:nië<¸u¤Ô<ÈFjxï;Þ°9ï1ÅÛÂ_a|0×r›´%vœ‘±?¦V<ҝ|/Ë2i&„N4­K/úA].˜Ê‰M¢›ß’Š-ˆÖN¸UaÁê^:Ë}õS/XxQxØO?ۂ|]ÑB: ¼a'øM¢wÌKà^`$sÀ‹°Èƒ\ÐÈ…œÈ ÍÁº-rÄ"OLSÂ¥DÈ›¡“Y—µ"‹@2&€R*‰Ä˜ö,Ñ"É,Í Ù4N!é$OÔ¡tˆÁqX#+Äc‰˜,—9b3C›è£Me‘©ÿc¿jvÛæ•èÌ;h ÝEQdQ\€?bÑuÑUAAõ¢íûãQ¶HÙ²d[îðÙˆ‰Iœ3gÎ`HD•è³ý–£ï28‘}Ø¡ôeEhЍZ as4nÓ¢‘;4tƒÆ®Ðà%½º~  pnÀÂh¸ƒ.¡†[¨àʚìS +á`) ¬…‚Ő°–ƒcŸ¼W +â* _¢áOø”~¥‚o)1ßø?cák4W“#avL‡ùa°|-̐ƒ)20G +&IÂ, P•—%ƃÀ)†Íf†õnŠU·Å¹ù¹ ó—þƒÿÆx˜B0(¹¶È¹Gî.—àBu­‰¦S.úZM§\ôµ&šN¹èkM4rÑךh:増Mt_²ÇkÉÄ-ZÐeë»ä¾Måÿ ° p§A»/~픾Ž±X\'>4|ã 86$±½žkÒnj6WØáC\lãç& èą«?4q.œ‡¾NoùYßnÞgvøw{õ +{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Ë3Zâ)*VØϺ–uCØa¶Øq(¥™¨‘™i!LYdÌ#s•P"“2Z#³ 2¬‘i‹Œû¦mú%¸P5¸Ñ€#\±àŒwÚK .UàT n5à˜×,8çÁ½`xÈÁG^JÓXªÁV Öz°7X&s0Z€Ù W`ºã˜ßÚà*£"*C¢B*Å b*§u´ µÈQ“µ)Q£ +µ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áç:„  åž¡Ê 2`{©û'„&ƒ 3˜ v€ë@ý¡õòO؃MB™wè3ËА:!±s*=¥MFé”ý”ÿĀ±iGƒD„D…D†=Bd? 8EZ$bìy®3z$‚$Š$’Dš$ÿH#'{ÊËF7{df'í,t´Óž®vÒÔ¶–F¾vÚÙNzÛÝȹ3·1ÁÇàŸm㮝ncïãËöÄQ=+T£rÉËÈe@½Ñ®ìòb̋4•n^Ðy¡×¢ÐNr¹Èe$‰K&9¹õ_vGДŽ””’””4w¥ÔŒ˜J»ü¤ 9èšÒ”•RuÀYŠùJK9KÄMÔ5u‡É (s‹vïº÷®{ïº÷®{ïº÷®{ﺪë~#öˆ;Õ{DµkðZø[ÕReQ³Ë»¥_èá霥ö'"²²np…ÕqÂ¬_Ò¨G¦´N——ƈó7®«G]qyîsî—/<+?ÿYùùÏzönƽ´žÌçú©Ýn>ýxÞ¼½lõ'ìËëÛvêCñîëvûüýeS¼îNü=MDX^ØM—øÔ«ªÁzí;Քõ÷¨I—B FeÚx%§26^hGt`Wf $uɱÀ³ÐŒiÉl ZGçW+©¥”VFYÀ)i +è ¥æ¨ë¾!¬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{%"%*E2T½Ó!Ê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-ngqcGxG*15¤Î#…©ì> +‰•„ wC_3^v¾D…8ªzwŠÑDàªÿD^©ØÒA¢¢áO5€Pž›Í_¾gŒù¼oøÿÙ/›Çm +Ÿ€wð6¿’ø{„Ü`d“MnŸWU”,Û²»=3È"h}°[nÑ´T¬zõøð¯€ü§üº›™ìåøÂ$Nð‡íÉó´«™fœÝ¾ñ.f>Lð|πÏáe.ÆáÒ®ß6€¿.Ç×Ã~ìÙõÛcÅÓ=V¤=–“=–žÌÍkož&™lÐfM5Ít«­±Ö:ëm€¹ZNÑ&›m±Õ6۝vX6„Màf3[¿¦vßg¯ž _dÃw°{0{z˜=7ÌÞÌfOì^»§ØñõáøÈó‘ûôÓ'¶ŒßÕú‘ùƒýc¸Y@2° +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¶lM›%Û¦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Ì-¼¨ûö‘w¨Èb!3ed´Ð˜.Œ•ß–c ŒSÎqÚ ì܎Ÿý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þ;?†ý÷ endstream endobj 13 0 obj 12672 endobj 14 0 obj << /Filter [ /FlateDecode ] /Length 15 0 R >> stream +H‰ìWي%Çý‚ú‡z°Á*r_ŒÔêM–Åc0C{¦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 ¡‡àƒ 6˜ ƒ +2¿øÙOFøÁ÷>úà½wÞzãµW^zá…[Üܸɍnp8ØEœwÎYqÚ)'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ì¨ßPRßãÎg€ýŠ0–û_ê€õÀ˜Þ\8ЛPS([ÈòDÿS'艒!Éútù”¢è„Q  S?ÐÔeÇÔv M@€RG¨?M”ë ŸD0ÓzƒÐ¦îæÑç’i=âž5! ÂOiU¥©ÿY¤ŠGÊàHž>P_˜6¾†kh²H6¤ H½)8"|³[Ù—ÒÈSë]ƒ¤ HÞ®ãÙ/!1O,·üö_}‹¸ýʼnʥËã áŒÍ 0?Ž˜Ðü4P I&Í0‚æ–‰f•4¡Ð\âhÑ4uš.&!z<õ{C-Z–øFOœ}qÆdz;È!ÍÁ#gŸœ½rÆʍcšê™³oÎÞ9ûgã¡ì æF½´÷Ó}¦š ýl5ñ“> +nÞ°)À¯ñ [Ñ ¥m›¥ëʵÊvÁ(ŸVý¹yöâŽUÃûFtQjE6:¬É֊ÖÄΠ”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­V1"¶$$Íôô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¤à‰„åZ#]:eŒî‚×Ü~’ÀwƒmZ%06žx%;‘qÔÕXf:º1‹päpcg ¯O"TŽIÛY%ì‰u0˜m֊(v^(„N➸ّ7arùùNXçZXïRÙîYTÆëì"™Î£a…ÇàböUŒ"^ë¼£'ÖÂm‚ýšE±“÷[!x-ƒ©¥ÊNóɀ¨¼?²Úw*0b³LcZB²çԉWàÄ­‡eH)ï²õ1õ³#ÔtX†.ïêÖ*Ôzê©GÞbPqyO•ÉN+腳¼qòÄ{„ßYv3‰t}h9¸&—³A*0€ÜwÒûíÛëË_¾½ú;½õ9 Ñ1 ƒ$N³Kdh¨ ÄÄyÊô'Ûþûó vÇȪþM~µöHuÑ_°ÿá~‰´Haè÷ÃùÄÃR°×­ E‘µÀB6ì ÖzâßçTWUOß»w1FE!`æLwUwÕ©ªÓjº©…»“— [c†{wípšÓ'¬ò˜B±èª¡6É|O½zzO†Äæ«Ií`Š¡]ô˜N$š$b™¸î¨fUå~•Bjl܉Žg}v|ú…ÓQêwî^^=8quþvwzùaú† å÷»——§üÑMw¿âãߦã§g?½‚`g—üÖÞj?›Žë­éÎÓ«ËóÝëk;œ^»ÆŸ~:}~qFG‘Ÿð·ÅY¨Ð‚¾!’k£«:úfèÛ÷-/ï¹Ó×H" ­ªøÊsL0T§)v²€014ejDÃ*…ÔØ,Øõe™K§Ÿlÿ¶Ô0üS¸.re–…lAˆ²x8œ°ÊË5&¶Ô` ×Êl•B͔µ³Dì $ž{üZhMrȺ9­«hí×f´Vh¤kõ@ë¥Ó/—ÖŸÌgº~‚Ì„^0в-Ίyl ±êI%à…â¶ÊÝz±‚mÄ4[OʯDÎÌÒחËåOíÍÛðL°Vܚ‚ÑÍC…§ ÄdæPA%~±õuŠ©¹qoH›Š×(7ç¹×¯€Ä™¤knÄò9s EKy…“ ‰ÁCë sª½ÚgËkæ !‡­âÖI>–^ÿôtŽ¸°C—³ðft-̊¨¹ÚY¬Àç&Z¬ß òÍtüÝ»Ý닳‰ÜþzöÛ­£%0!¦r™¢ ³ïZ +í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ööhG&ÙÞ­C’ËÆ÷ӕœÚ^k繈nvY¾Ì {¾í ‘Z1¸âý¸I†ª¾eåªíôs¬y—iÛ ¬D°µüŠeIò· µ4ÿNêÖzµ¢íÍ1'®@kÁ±Wµ…íÛ£› +oh¥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œÚú2l» |W>^´8^- Û&%/.5˱qj^éqBÆbp/óto÷pЙÓ}Ùm"û¢·H *ïÎ1|Å+±xßÞ8#0>qeŒWU½gݞ%uîlb”~g“PzAMV' B/72.…½>©Ã«©ûβÐå J”¹Zép5̇L!Rå©„ÔSèm’ÈDSʤyMrdß3èRÖW§÷Å~—œ,%Z2˜¼Ólc>¬¹¿Ыeá!Ájånƒ·8¥t©R)X½Đ^ÁèÍÜƅ”˜‡àQr_óuÈљ’ئ†»U´Hä3ñƒ1*#9…s‚%£Û‹ÉZPXê“ÀÕZ;©Í(×(ہ¹ûCæ5îMn6)K0¡9GIZ[U’tãåšx•ÊöU×ڜ5:Io,µEQŒûý%Ê©"É$YŒ¸²&Òßy»'I)JëÒÄúRPö§*`,Yxç +R§îMˆ+¾š¨”„®Q8õ[ÕäXÀØÙµ°2g7ÑÑïùèrìቚ”€¶V7€ziWumòY &^‡ñ¿&$e¤ÄӞá;sLÙ +48í$Ñ 5ž¬š8d+ˆ°­}¦Äöâ`8t8ti!ÁžïՕ,`´NÎ|PÂG0Nq@íLb5s‹ßª´ÊÈc•§Qˆ±¨ÑÉÚB–X$4[#vôü! Å£0öÚÅ™ÔÔÞ:íl®6µ_Q+DÒDµ¦(XŠl&½Ø{YšSéF­îŸy?äŪš¦êóÊÖÂòœåƞ®¥“Äí;>@Ð@»ë*€ûifÑVåÆO=i*.¥$Kñ–: +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&tü@X­Ñ¶•»{ˆxmz8ž.MÖ*O]Íî‹"¤\úþx­åjæC%W¤\ƒåNÄN+®"µj½@µÚÅT&$\a|rŠÕ¤­%S¤ØìæÒ;Ž¤•f™ +±à¸ %º¸>bän~.,$×8}@碫ý9&­ ¯yX*ì²*—|©©sƇ*\ˆA²N¤ÓÛµgçêy;0^“„ ¯—j¼Õ„J”©¸”ýîcŒ eaÄ@Œ>«í¨ýÆKÿ‡¼x×_²®º~7ÂXí'G³Ã‘n,¥£&ëâ ]€*2zmÍÙ/RZIþ/ïՎdGîâ¸Dý;«ÌýÓE€ý<žuôvÃFIl‰"A@¼|ÀŸ‚[Ýz¥z_iû°Y|Õ±1f}™¦÷‘ÏÂb+5xÓcæà=»¾øˆ‡·°7¹/,˜Z;2‘T+ÈEÞgÔçÙ᚛ÛI¡ásÓÁòm­n«“Ãð£>&ô‰Ö¦iYÎámð²{nž`p{:µyËtAiÙnšå6áZ¯–.Søa+3„¸LŽšÈÁbR)}nrïr1%í•q»p­©kADA€®¨E)˜ ?“®ƒ•`Ý.ROSOh"€–²ŽÚ˜:ÝÙi­ŒÚ\¢ñáö£9éµÎM‡¶mçþ’Ö¯ãÑLã×5½ƒ04mðOÃ¥O€÷0Á½­}fÒÞ¤¥‘üŠâÆ,ÈèWð™à œÖQ#­Õ;Áv²bçêIaóH¯É.ÇÚ¹ªÚ£‰ÃfWÅBŠÖ 0R†³¶|?YÂg½bÞztUÛ;˜Q +Í#ºÝֆ:Y¼¼ûXšÿ¥eJ‘€)ÎÌ,tz– Æ0ٍ1µß Çè/¹ŠÊ:?çÔ]Iâ R§©4Ë´Ap§,émQ¬žGúwӎ­×yƒ •`a^¨öÔ1P›M)<‡k_ޅ>jù ­tç¨k7>,ä-ÇB¾Y¨÷häÞm"ØTp•›eãa‡úà֗v% ÊYAO©M…™LòZðe…ài¸U‹Õ0W—]ö2”–+¢Š,×N·Šq8M‚çD¹¬ °ÝæÄZ L +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Ôˆ—ð¡ì|±Ýœ·óœSls<|†;ìtÁ¨¥Ê,+ÀÂË)56 +Ï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Æå’DKbÞE@Õ¿*œÅoð^¦z¸ÑYá“VcˆMr‡Û‡L¾(FÖ!ü5ÆkëÔ¹z:¸êð@n†¡,ˆ<΍N䔟£›õ©A:šmr\–ÄÒ^Çí#Hè€e$ŸNo¹]ú¥qF&0“{D|5óñÿ:&Ábêçžô¾‡JóŽØꋃúÊî¯MV²H“`m³ÂdIðœC?Z¹YÙ.ë$•¶Ïi§È îà¬íŽgjϨH é++xP´Ì +^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×7­ÿò)Ä ˜œƒ–ÔV#×ÿ +“±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Ýô+Ç1UÓÖ¨ü<óÄ µ¶h€J;ãxÁÜ»Æà‘ bbÔzËçϧØv[•l¨CJ,Ý.i¨SÊ ÚÂ~$T½AÁývðœ0¨`‹JSd€° °œK"À+íU>¬O-Lߘ6xˆ6@i®<jH) úö¨Gš±EwLRNõ-68`ƒÎëŽzÇç)Ûâº{Xðì“R•íì`_“R•Ïç`¥|êÁ\Žÿ §´LP îç´t°¦ñ÷HH~¥uÄLÏóϵÄFß]ÊAèÃ䭙ûQC’Õ®Ÿ>ø0µIíö:U.oƒaýŠÄªµ“maËöÊíÂeKWjx@kV]ìÊ-Få˜A½@à0,ĎI˜ÂhR!÷ÅÂNÑÁ0t –ÓWž¶šŨ×ú`¯®Ÿi»]æ1@Ú½9õ +í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è7•.l;F•7§Æ4àÒÏo\ê‡{4¦'#~KMNŒ‹*èüÙ@1}GT̙9MWØ/ú¥V°J(ÖS‰•Ô‰ÐՋûORŒÓmÑÚþuhP”D¼ë àðìU띠yÏ¡ã fïÈÀPÄ‹­œûæ=6³óû¹(—“{Q¹Òyµiís¥ƒßÁë`ô²PHќ%ւ6šÔÀØ&pªéû4À‹T¾¿ÔÇE‘ Ëj»2ß±«îãšô>*V×´#€’ÕÖzgX¨œÇ÷Fß*{é×Î9èZ½KÎ8GXÔՑ·ºz'¢²RÀUcÔn9Œ1ëNÖËL™æEQô{¦|çqÕí´¢ú;[WÝ1 (œ¢ƒn€Têå_yudj©‡mg»€¿jÈÁYªˆ×Lδ$i›Ï vÏÁÔ"­¯VgÁGêö°‡kI©‹›ÕŠÅá#Êi3h¤ÂT5K/QõûÒôý))ûŠáµz«, ¬´éü‡õjÇ±í¸+ð&1 ¶Ô’ÝmGNd/À0ØPð$@x¼{›,Þ«™'E p§Ð§¿d}„7s¼9’Ò¯©Ó6:xtãÌ×6¡xÜÔߣÛõÁQƒÐ!úú¹f‚ÐLºê1y³€s¥ä„©ÔmÒZÓä–ˉ&j¯ekˆSZª§Nåòj5A=€InßÓ£Fë»Àdƒ[ýх“lì¬mëmm»ËfÜ\S³0º»÷VaZöšœ³s»m‚/û¦©8Ô F .ýIs’02‹Í¥TYX‡’á÷²’–†¹äÞʲ“V&pF½&:N!õ1øÕøÈÑÆÛ+ô±såQ{kœÀf0«Y§lºj¬ZlÓõTE¯ k£@¡ç9Eo¹Ùå&!½nÀ(»ÃY-»ÚÉïÎ㚸Ülg;G{ݗ%qc1Ê.gKÛ[ïêý’CXÚ×wäÀuÎ΁ #ÓGº2Åb)_†úD¸·|3$z_=“½°÷Ñ¥$É¥#ð¹,ÇRåßïà÷žV?%l«`;‹´ºšrìÒ)c7ƕÄdE©¡“\£©ø_âõ{¼·~ŸçT1®ý¯ó¼/a™ï£¡~ŸW>ònÄý—ÎU—³„hZ*`>¾röúå^?°0]ƒ”¦‚—…)¥Ânð-·âð j[/«8ؙ7[\÷~Þ¼ÓéñdÓûàÆ>%ºfË%¡©§Wô´* 6‹`Ò»i?pËê›SFyϊíšdä¡[làà7)• =îÖn‘;̼bÎɯ‰šp𺯤nV”M’f ãÒu¬&Ý~4Ö§ºò—_ɀã˜-Á•L}UÍ.Û{¦°«×RKx-‹ Ɠã‡Ñ< ^§œ +œ&P‘°²ܱÅSòLT| +O” P»ñ©’"Ôú%J`cmú7tÖ[lø‚{ÐÙÆE]æÂEލ˹;K…;!ÛÍWrŽݯ,tû(Á ‹°aý‹M]j,R\ÆdxÑÞÄéô¸µ7/ +pÃ(h•¶Ã¹ä%_ØApG½«wsbx—Áã+×G¿LŠãÕþ;*…I¿.#¹Òµ¬½&0¥Ïª ¾8+º°—{>à²%ß^0WŽ¥À±,¾ïÕÅ!-¾G.Qû9ó +!µ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«¾œ±Õ/çg·³¯®ØOÁ@€‡>ÕÜÑìp ªÆ×íüZHF:„_gñ[yú÷——=°ûHosÁÆ2ögï @.k®¯5m'ŸZä Œ…0îw¨1ÀÑ-AÛuY(æ>FU`â–ú•,n¯Âç¡Ëü~ Ÿ%Uœ›¥µU–wgµ5kZvý}+–¼æsc1l5Ïʽºj1Ü~¨žcÓ#GHÝm°¿W%nÌþëýËÀòÎðñyê¡®k$kÞU/ ÈâÈ1zÑæš4îckM±YØkœ)’›å6=?ñü+…Ãs,«¢ëoe’œó„í÷ø2´´¤*hu.¾VVŠÁ"dÿšûFÎ9ÏIÎ6é–C¥InÍkÿO¾½±Ðü|ð>TÛ$¦5£%C2Ká@³«.¨‡=5wt¸›ð•ûð#IÑ Cʛѣ_)§èôQBy6ôAT%2“>8g¹3{ÚÔ}d~ Qi„ ÷'Ò¨mIi­üÁ,܎ÔK‡A½c;+Šï3f|¿u߃þƛDKO[¬ÞsDyÑøy¾¤ÃÚ[²|Àý56µm„ ºc¬*¾D¿-Ê»¦—ön5É ÷{z}½zî×4Í})7u¢ªîÎy{3ÚöÙé9ð=m{÷"a á$¯Œ#5­&•g(Œ3­ÔM ôͧ-Æ hûbgžSO4øºè lJ{¯@(РAaÊÖ¸ ìtD±L’¢UžëOV¨q,ÜUVÀÓÈt:¯TÏv†?HÐ wY¡á¬­@ƒºÐO(mqïÎj‘3Îu¸”×pLpžÜ ³ÄlËËÎ<†0æžígåöcìÉô {Մ`ô•àÏ¡?[Ü,ÞdÐÈ>Lò¶Å<Ì`{$B×0ڐYÒª ¯¿‹á¢e8*~zÕ;xE„ýaÃ;ùßøã?¿ýî—Ïûñ‡Ï?þüÓ¿~ùßË_}õçŅŲ¯_¾ýÇç_~üé?/_}ÿýw?üðßOÿùó¿|ì×/ÂÈ¿â/K¬5ždxç~JX‹‘ÚLïåæ_i³R¬`ø ’j +šÇéE€à›Õ>ö(ovWGQrö`ù¹dµÍ>õwšÈ{¸†îÎïµ8f¨õ LFŒÙdÕïnííR¥œâ÷)áí§ÊÎç¤ê!7‘¥ p=Þ¥¾»iÀ A`™-rg•œG:/_ÜÁ¾©ïv”Ä8²<ý$“tYü5O}3š ëóý$øæB%¤ ƒ0Ü®ö ünÂW²^¶tZ¨ŠŒ±Ë× MÆXŸ"À±%GN£ÿ|Eã·3tÑ]¹Xô$(-XÖe@9AƒéÕ\l§[2‹õ{¹ÕA¡O”'Ô²0°—Ÿ{0›z± ¢6(Qù™ká´*9RúNg˜eäb¨œ·“º¤Ñ;Ž5T$#\Oª'bZœ«šÏá(ytlÍ +ÔÒKù8ÃJ™``"ÊK ‡ªP÷.œµebuF o¦ˆ;ÖùŽ#¬ @ô×ot‡ç]mr¬vê’n㬖Ê.aÃ9‹„îØö|ºoÇ €ƒ +(£skè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?ÉdBÏe۔Ÿƒnó¢ì#ۚ“îÿ³^e-zGôÌø^öCDïË£,ò0Y$„Œ,œ…Á±ù÷9Õµtß{ûÊzf̨¦»owÕ9§N©– Úmn”ÊБ>*_d¾¥`Sf;éçcd;6§¤ >[JK*!•´0Û>óç]K·â2>¤•Jaî¯]™å»&’”/`V}hÌV—PŠYò”Ûªƒ]'‰Vƒ¿ß>eÁ“5×í°bE;#s°Ð­cRoœ²È{«J6xàfú®b²Y²P_Êjã'…z 7µÏý^ŸUûÀdk|5ø ¤²9{|=TöçÅEçÀSúSêîÜ]@Ö®ÄY +Ô•”?. +ØT0-`+©,Ÿ ‚…†î­Á #}Þéþè¼H~¡©Åkç…吊çޏ7Ê؆¾ë“Š#a]º±<´2{ÉMxmLÅÉ5/ã :¬Œƒ¸u‚ !<•!ÊÔB«£W&E¦WÊœ@í‡c¨´þ@"Áï6è¢çվ̵Ò7)Ì`´“‚µS„]kº6+[)á±}̤ؗ×kÿìÀ“d ™Æí*7{ +:¯¦fe]£’¿¢%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Š,Uq£ÑƒýeóòÑuñ0ˆ&‘všN¹54 œUï‹*áãTµâ5fÝu ËÝ>Ÿ{T×_K³ýQ%QýÝIFNð 8~¾Ž<^S‡µž„.ôª©³Øř_•åKÊV‡Û*De•-š$6aQ‰Ϛ™RŸÐ­n™ó#‚1­lha êz؟28^ìù ½úOOýñÕ׏·Áo¿xýëö÷_}øî›~úï?þðí?¾ÿá¿yÿý??Â_ýîýLJüãáÓ×Oîñ?o?>ýôä¯ÿƒßýÍÿÞþÿø ~ù×#¼Jôøíã¯sïhÃé}!£þ +ÝQÿ—cØagE‡¨ ü\#ºõyâóÓÜÍñÝ~˜ÖŽÿUü@þýpI@8Á_|¤#bÒ)ԃ˜4ãKÅ¢ +ê€ÅêÕÀÖÔ9V*a…´oȲî7jIßÀñÞÖ¬Çmu)sGßÙé*Ëde`éxñõ) >ò|!^‘=‚ÚZavC’ý‘ŽK!êòèA‡\ÏKP¯ ï!IyÄKø [¥ ¯è= 9‚É‘fDªYEþ梗ý‘æ®Ñ?p×ÙG'âåiüŠãÌPðg® +®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…›B|)§^z»j\”…¯ÏhÒØ ×…×²^Úæ~(“ø¿eé´3¸U–C—Ê Îçª#Ò"Dë$ ßf ®åºo{Ÿ*;Y¼•†½Šlg+M;UvMÀ[íñ¶…æûüÝæzW•kυ]5{+{!݈î¶é­È\ªº‚ØʺÂ]ku ÆT÷ŠÜt) TæRWHcº2¶s®Ôç*aÚìšÞ>•­ØÓµ€dCÛ¸°t¾m;ÑÖFu e N“†×h¸ˆP#©f¿~­¬<á\YyïiˆÁ#ªäö Z…KÁîÛÖEu®µ­s†¹Jٍìm%r+¦ݽ¨ñÌÀ[Ý@n Ï-7)¼Ëö¶.×^K;ÛÌVзҿéwm—é- +:©£´Àä «­>`^¿àG4¯¾pIžpf°t3ž&ÈVkJÛ¸ðõƥ­VÅnŸÂ÷YŽiÿì³E•æHŸ•Šš p–AÄæþ!†)ðþ"Ÿ‚‚ÆâË5Óè5ÅF ’˸ÙT!C -Z[à •´§Ô¤Þ¥gÛ1cð¥è´°€¸¦€3º|BF¿ +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½9rŸ=‡›ãµ4ž&'¯ õ< Q +Èœ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/¸£Uõ´¶uñ• pøP‘༔®¼C—ŠÓê‡ý 0¦÷Z€Ñ7z¡8cü[{n{cÖWƏòr4µ®ðÅ~9Æ­a¶bAPÄúÚ¨iŒ Yر:±­¸LšÂÂ#Ȓ»Â]×ÎVŒ ö@¢\R(’“¬ÉOÀì£rÄi0Àb-ùAÊ GçÞÛßËs_ΌÕ×î2x—ì]a2‘Œ¬K ‹Ó^8¯Já¬ßŸÚ>ÿ’©7çüM«¬fxxâ"¿w­a(=KãW´eîÐ:Yái•;Œ +á¶m„ìNó¶â¸×Ñ­æîÕù¢äWŸÀÚãx‡ø;¶}òÔDÞ03ÿ¦4|âzªVD ܧÞî‘4E`×ÍážØºEŒ›B†âª¹ˆÅè½ÁJàêñ÷èÇXxÆ%‚Ö®Âþ1ãqåóq,ˌaŸL^ËLÜHõ:«%xC +–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 ®‚à +­aAÞµ\G-D'ÅX¦¡ šûá}Žù ‹ILK$ê—sK•Ùꨌ< ó¾r„‘lʉ7ÔvÙÊÊÕv)JLH¼{ãËQ~¾Rò•yiñŸ«Æ g»î1ëò¥³FGȚ%Á@¸U“¼6%ÖxëɘÇᶑC€Œ»jÂøxà≝q*†ÃÍÐSgaËÅdtD#lñîl›(½X®m1rk ¡ÀõZÖkg´m·š:s™iGgNõ::ÓÇ iÈZð­g»äp̂Ø6˜O„c̉g>R0ÇTX‰#AK´8ºÍ˙YƒöúÔ·YˆŸ ¾²ôÝÇ"†"G.8³F‚ÏÂdÆÌqö¨­·í§|­­7zmAIj+퉘ª\Æ£‘áÀâR°­eZñ!µU°¤€nÅçãM­¸ÀvíŽÞG%…]ºjaÖ"®µí%Ÿk{@_%‰F(UQtHsEQ NÂÌ6IÐûÂ2ƒv +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°‰‚}¯a®Øñú`C0ÀïS q]“ ,Š¬´©mýµ¿vEN´•‚Ø  ‚wџwŠ?³¦ò–iOû(.(Ëj€ ú1µ¯í5P¦{%|imKW Ýh­Ÿ‡ $w=ͨm ’«Û;ൠ+¯·]Å´Ò.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™¨¹ë$ÂCgEìÆÁ#—FìÍúR1:`h”Ü8òCèC`€G´Ô¾Ù3ø²Iíl4‰ßeKLÖVÊ£9üØNÁ.¾BÄsh@0ÚÔomå‚þs]Ë÷§¢l=Ž²TÛDòKõ³ÆæDЪ.ˆÕu®kðH¾¡"†ôÉÆÓ€°ó!p8ºÊgƒ—£¨¡XŠÑZZNÄ dúÚh›{qË­[”-> ìWŒ½AXèÄ)~¹)’¼+=„Ï0Œ Ë¢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ºªžŒ5×ÇÝüOmÛìXiïÿïFæ³d endstream endobj 15 0 obj 17025 endobj 16 0 obj << /Filter [ /FlateDecode ] /Length 17 0 R >> stream +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­¬F7Xœ¢v‡˜ó4܃r·> ++›‹Š£¨µmÄY ËΆH‚ +È«F7V›ûP§Ê¦'Äܵ¡¬)Õ϶RºcÈäFAObÈ«Ïx+Îý!s8YY`Þ$,j&ÝÂÂѺ +ôTÛ€bË\eNÞp3ؙȻ³è\ɖ¡}îúŐD¦hîúsËÙ[“cVr6–lK%<üésû$^N‡ •æ®Š£¶!¬Ý:6jë +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Áñîï./ìÒx·Ñæ@Ð¥ª/µ•kÝ{O‡ƒ%à5°ÁÂr:ÉØWi¸ Ý0›G¹ŽñYX$ç!xµ"$.Ë0‚­iÂÛ¾›'‘fñ_í»aåg›—Ü«¯êwüïQX½Œý1_*÷ßJ³kά‹³ŽxeƦå7âRÚ،êhqN¡d;Ÿò)!L¨cŠš™jŠU#X +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ƒúýɂ"H#çê€ +&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†Ÿ†…û‹˜@Éé&ËÂp—MΨïÍ-c8ƒ(ÀP$· ×á¤Á\B«h?¹_}éÀ\6.Ìö(—9—W€±‰›¬.I8Ë~7Ànq+ØËîB?ê]Ñ%ŒÈ§ZTº†í{¨ülùŽ%%C ­ÁƏ"Û¦ù{aŠ{ß ŽÐ>5öQ*c¤ Â~¿ìî$#E„Ç\kcÌD#á |>ÝZ‘tüÛÕ¶šéqŸ@ïðß’‹„é9Ïe,r‘ 0BfwɉՅc¯ÉÛ§ª»úû¥•0‹¥ÒLÓ§êê)öâÄ^¼ÝÚo¬Ž8ÙÈS ¤ù4YSófc-íÐ:ògqoñýacj Œ½ñÂëQñ€*adÔWGa½â_=VquúÚÿhbl*ïqhSV+xß¹c¡ÖšFË×iýòJch&órÃ|X|Nò{žhøÆJ + 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ÞPØû ¢ +š,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 Ñ°pgú>ºØÓCø¾çÄë˜ÊåL‹°ð±!Ö±‰êYÍÙ`$R‚V÷ ³*Ä°:«BØ»îƒm֌"<Ç1$¶% íÐuð[á¤æ}4Sõ³zËäk)# q\Ý£ÝïoŠGÿ "ç÷ó©—ÚzãþËÙ[áÄÈêð<$ùŽáŽPF¨mÀ.& “½Azõ£¡GY[ϋ1…²]{Ï;öå!œCQéÅO]ÎíÈÙF˜›mEçíÎZàOµfÖÅOAõR×êèœÑ·Ã;©º¬2¼¬ëK֗‡W0çØØ]ô³6©ÌÕ¿4Uq1¦:¤ º·øÙʵ†»am•Ê6 ¥8?†}J„A/¾xS|~¹´Þ•l#î¼âO=VtÐtxCØ5,|nē)³büƒï,êžj5¸çmf_Ïߜ¾” ºê\«Œ !¸ìȳá+ÄäºõôÆÎÓÃó‹O}ûð‹ßþ~ÿýwÏ¿ùá§ÿþóÏßÿø㧞üæÓ?þõü +þå·ÏÏßþôñfö+Ö)þ}÷3äÂOQ´åf·âÿ}÷?üòüðo@?ßú폷¿þ­Ü>òÂ_~ÝÍ'dBÝcÖ ðÿŸÑFwøé5ŒÐ Ç^Xx»_ÖûþôO³Ûx”,EÂêXtqi:õFaa“ x.²¯Ÿ­†Ð9­³o:I"X!âç¹=ÊÀ!%8ì+\`å96VmÂ&D™@ÃOzò¥³u ÌžÏu=álYÕF`TïºõH¢õÐT Zü¡£UÀgÁ-ØiÑAˆ}Ô$ÿ:]Tã½´  +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„[ôæÿÂj¡þn‘ò2ñI°ÿðÔãðãò"ç3FŒ hÚQ]]/† júš'¡.Éâ5 + Ïb—ëž¡_e–OI³Öõ±ÅÏã¢Q 0XËY+À2{V4B5`n}OBÍò0’~”Ÿµ»ŽŽ3–À…tbØ¥UëÛQBµÇÑæ=Œ­œXxC¬é>·‰C³‚¬X~©÷€ú°¼~¦W@j¢¼½"å@W½ª«S¡UK°¹žFĊ0jîIè\Þâ@»H +'ےU¯HYµÝNdò²ºG¢—ÿ½Æ—ЈcÏ4jñTtÇl÷û¥·@ëÉî”›n3–Å`‡UuJo©çДÍdÕ+P`Ý¡›“fôÑPódÏÛ$`Êí!1{€Ûê8çLìŠ*šÊ6ૅ® Àh¡Ìt9²é©ºÒŠñÌÛ~\µˆÕ(£¯Í-lfØ3ìä`ž-™3tßj¿°û÷mw͓«O±u³ü~™ö’¬5ZNk:;ÈÌú~Ž€«/ÞL¬e{âºÎ–Ð>ûž×h¡Äi2Öº°"G‹Õ( ¢-ȹàï'†ýl¿Ä¬ )S!aaÊ%0|uªÜL.•ØVãKµõíȧE)óAZ`/6}ÿ([ù¨W¾Ðt¯¨Ö!^[’ƒ”™#)ôôià­Ô‹Â{´EÊ×ã–lQD7‹À«TP÷íd[ïÙ.[tlt‹aPMZb‹Ö“]JH_µ‹Y½™lì{wëPªçWþg¥à­,|^³QµÂmÀ$ÝzsApÏ.‰w|^“àïo#­öQ%~Qö¦£­Fµ‚ËêÏ +¥ Žëº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+ý:=ëÅ8Ħ}Ý ˆ8‡âEÝÝZ³,a»çwznLYiE×ë^–gƒˆ8µ8K”Ç‹Œ÷½½Qˆ–¼»†N&—¿€¾r>£‚u¤t¥«Tôç,ÏÕ¤Òg¯3KyäŒg'(Ò†®¾Ü2ëÚ|Ø+,³§Êï3Ýõ£^^a0 mVÇuÿHù_eÈFôm•©Ø¼PI,!¨ÊÌ:¾‹oßUdzK¦»H~¼¡ä ÒôÚñPåPXkœEÿ~ßnW‡îXÉÎlð¯Rð¨1‰ñ”˜hÀ>/íÐJ|oº”Zz÷§aë—¤ÐV‡‡·u„Õ˜ƒ­V÷¥)(E©lÍÿæ¥=VԞ°n¯õƒ½jBý'‚ Ìaå}½ˆí“Be"³$›²ô-HaùT©CÜ=¶2i/^0ƒ 囖½KÀü?ÛUØjîÒ#€1s×Üâ͹ÿúd,‘¶ÉÖ*ÀgŒ-KÉFÒª^CB] &©ëÒ +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È{ˆw(\‚ÉA¿m7Ú.M÷òŠ ¦è<ª™cWð}·KrüÉ“³PÛç%|ùó$ØR3¡FÁå_‰W=à)áeæ™9ß:ó*À:»¶¯žNhªCË +×âœî%œÖáÀLl|*‚MJÏln:õWôÿøâxÐtc`J«M¢©p.EŠ3³x^¥pUŠç™ Ï}s{XÀ<ò‰ÌÕ¾)‡­ÝS½$ý¶—@}‹ŠáôXfѐäŒ5°v†(°S§Ï…T"Ú#‰ß<-V Gñ<Dœ»ÌyDZªXjCØÏ X.s¨?"ã1ƒž„çrGIípm>®¤/°#èò.È˜1Ç’«,)#&ê)•ÀtŽçÜËí¤ËŠ›5óÐf§+ž„eú žœ8ôT܌Nüֆ°ãÚÑj•HM–Ø·ñKKz:P«’㴏±?È2Sû-¬hŠ£ 3àÓØõGf0X+䝶· lSjÖÅÓqQ U»ˆv(è#Ò4À O÷ݾ×}qj7eT³r´‹û§ß•5F˾O°¶$“#Ù¾«¥ý Å­šàJÍ¢+(; £vÊ oÒA3lådÜâʖŽâ¨˜ÔFý*6äwßýP‹Té³7}ª¯"鎳zg0Òjz–ã'¤#ÀDž‰1ß: oUfZ*€|íÈv±yõÅǒ6¦fêØÕ Îlb¾ ¶sÄ8ñœœ}:į,ýêó Ÿ{ÀUý¶ßZ%<¼ b»©„Ï•µÔ¡¥èQeef §7¶²âMíVü&¥›­å÷Ð"÷7íVÐHÕràsÁ\š©öÃ;öõ‘™DYø\? “§pº*)t‡×èÙ!¼wµ†ßå'tC;ÖöÓ×ò—29Ëè"΀ËÒo•¯õOuãsé}m­* 0Õ!8`äXÄÚÍbµ{ÎE€–ø=‚—µ9ÅçFK¿&k9ö±f€{½-­uY€â–”øËÚÁóžcC‘dAÇ¡'`à ùçû¯—ÛMüu¨FO·– …ý`&Ð`bKÐÕ ¾Íbã6ۋîLÔüö9ÅrTWŒC†ƒ?àÁ§sJŒ¨ÖQœX-Ö$}ö¼ŒYî(‚ß=ïøçSãb¿>¯°àìÖ¦úÁmTR€‡Ô›„Z€µ¤`P ¯nè:³w%Ñä2(²“¤Ô}ê-øŸåWb¬(ڑ] ´Û¼ß›{p2½ð53wÌZ椝;ºß>¦(Âo\“c›. pIÕÍ5wª-üy]ŠÆÀ: +#%HmmY„€-ç/@o4sPŽ€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ë¾ ÜÓ]u|ýR’½ŸžHl¤óè#“¬þݔãŒ#JÖԑØ>çPÿÿù<ãBÊK霣öˆÒ?Fåà$fÂFÌÖM¨§æ ŒZÔÞ5Gcû˜-á^wgÕTëšãk +ÎýW…c©ÏSˆhÍ4Ü«JñOüºXTÝO‰ÿ$ê,¼húLø€}ʉ %‰Œ"}TQh(M–Ö:LKçX …–àtb~ý[,ͼ6Z=€z±v{¬£ó‹åÂ-‘û2*ØGßã®u¾ÿÍÇ.¥ñXü—Uj¹11è8¸¶¼è9H°vjûœÆ¼4#KÇ[.&ÐL9Šm:Ór¦f®­iIñ°'öõ©XUĨGèЌëÌâ§`>X$fëàW©âBõX2gÏh3°v‰û1Dímfzjê€^•Ùvß à̎å÷M)ô4$ïñóbQž5dlCîž*ïʦkròÇbúKRÿýˆÇ=;nšÁE美vVî‘Ï÷=ÑÑ$GèèðR?T#©ÁêùK¦¾‡øRºÏ&°2†®– O1‚C›q!¨Úw¤Êxûú÷Jã ä]¯°&ïukùJgmåZLU…µ‹ÆÉaqµ·ýÞMí3–ÝÀ&ûdô%²€oÚ])×Í0¿æQ^NíF,»0` òBÀSéâx‹êvŽâ8znÜý4X§z³Î£•K»›³)²¬Ú¯Û÷û½") øt0Y½_°–¤K<Á¡¡¹¶Í•h—ú„¡ó´rkÞ£Oö/ü–±<Û½B¿J/¾eW"´”Š±2ôُ$»ÇŽ%¸(<ò¦–Ø|nÄê• [Y¯°ðr•ð:`j¤µ ۛAy¹‰ ¸ž*o|ëƺ«0±%0”ÖÝ?- ¥×ªb],"yØL>L;*¼¯td÷a²aScÆ<øއ¹ Õ»ynØì¥ ìú3ƒ€Q;‹ê•!ôÅ"Àõ|Yhá:_-SYq Jƒµ>ªµ&µ%~©imN“¸ Á¨^ÞlÝQ—XÙuë^]½›ÔÓÌã#¿¹ßænz°:µ´MUÑrí¯]=³ç-äó^•|MV¬¶©©5H¡ä6×.UÒÉý#ûõ«ÞD‡|.9}A¶>”>(íºèR’ÆJF h7bÇNrÙo–ÛÜ1È$áy•z(‘õõñóº5DmÝÒy`.Æ]\ÒïôôaÂòÑ1»]øÏ!¼µ£´ÇZI‹stŠ7Àµç­×ä`€¥¥âݙºWˆ»¸!L”8ùރ8{¢hÎýv"SG×Ûó•ÂgH/ÇÊc\ÙKÀÈG²)™5ÕH°‡ÎúÇ´ÛZütB×ËuUÛÿܟi÷ùcàþ|ž#`èÁ ÖdŠ`]LM‚Þ9„΄øæþj-É^´îãÖˆx”NFbåʾ´ýˆ¢»¤ôŽëç°Üà/ð˜—< çHeü>¸¶Ò5¬í: ÌΕG¶é£˜BÌ¾ô±”ïP÷a sö2Ûí?ÛՒٮö’\øoyœivÑÇìúH‹Ò©¤‚ÆG–m‰¤òóm½ýzªÙÖ÷û¸Xt7}ßð<7°£³‹_j93.?TÍ1óö»Pa”´6æÚÕ%IxëJf{y>0h\ÝG1êá!˜éŸõó D7ŒÔ:'S€ösÂøË㌭h¼7f{LHiWS6•ƒC¹Œ¬ôC½îG)ÏHtrmŽDUMp° £=QkO·q]ÝÍË(¦² Sg½(ÌÛâžj>n¥šÐZ{ I{v]Oýëö¢Y9mLÙÓÅwýV±Õé~i;) ïÄê&Çöã+ 9†&õXpëÎ\8XþÃAÙÉÅÑZÃ^œ{„õÄÚ)ôT¯E-ג/’¥(Rý¶ÒxVŒƒápP&í¬#üMÓHÒÉMɶ·äÖ»öϙ†ÚkG©†ŸnO´.`e6ÓôHu¢ïó!ß.PNr°µn“-²UŠv)æ耐NÈ:¬Ñwæ ¬=-®I‰ %®·ÅÇ8NKÖö{™Î:ÿD0æÔ±4Ai+w¥[oïtg`pYJ_k·Ç Ê3EVت »tsÁeáßoE…ØwÎ*x»#SRË6!è^˜¹™æ¢J/WµYßioӊü׺T¶ÿܕ‘žÚ5ÀŸÇ—ÁD§Ä)âŠ[e"ûjÙ ÓH˜J^ð¯ÎG¹Ú¸ÏâÉÎ [ˆŒp 1•ö^ºRAb]ýV+×âáµvY°éT õpPd¨’Ni·lÄvp°õ¦0L +ú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²de´´¨^÷j,j{õ.²‘z g¥Ý] F»^ãFuW‰¬º[M€£¶E$‹ ÁZZ€ÏѨsµš æŸ^5 ÜS |f¶b"À8µ.¼}kÞj¦…¨SÏBÝÒw]±_á|–öêæÔddŠ!Q‘Àh1Í\ÎËú½B°nqc‹ ÛW®ËQÚG({×ýlï®ð¬î5ù¹Ë6×ö­-mtõ!ìÑ5YZwgˆœÊ’M¯ËjWÃÞ_ñýòZ$t§'|¾åë1NŸ¬î«ºƒ<ÿQ“>U…#£šbX”£iiLV,à¹Â€GcÝ}ÇRW=6E pŽl ^Â.¯1Ÿ+¶*kûV¼ ª¹&“Z¨ŸÝ q±ý2W\:ôµ«4¹åќtF«›×²€Ù7¿ƒ ¢rð‘O3A-‡4åÉýKÕómIS*•>֎ݏ‹ NŒGÍÝçQ~Ý"S +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ª^þÁ·¨¡xo„ Õ#êö•ß¯™µY¢ÃÂÓ *lÙ£âÕK`:Î"ŸjHYHls݈Cu€¡sG€Ÿ=Cbßûƒ?  ¿…êÛN“äl·íàÙe¸n}¬î†)ÜŒút¸ùÂ=.1ó’T“{Ü%³ߛ«4÷jÓ´ܪPËýã¯|¦+Œ#T§ôp…Õî›Ö,G¶…‰< -öâ +ՏEqL'AçÒÑÒhžÛ@%þéó1…Žg§n%–öüüWöeê;à åKe>òMÑÊ<›•Ìúõwè÷ë¡ÅqfX¨¡¦Žšqdòöž#ù¢–-•s¬‡F~ÆÐ ƒ¬ÊÖöã µ!Ñ*ÅF Œc‹¼é˜˜Ÿ>j?ô„¥³õX<0e´Þ6ÿ|’ê.ë׊EV†M\%ϝ÷úkñËÑΡu;+ÏVg‘T`óì0ú#µõœ[ˆÊèá£wšÈÏ :JM†µ¢{¶·w~-DL±,?Þ{9ØlDÄ_éëvê µðqmÉÆT&s_°Z¼’’×v¬v§ä +•öñà`IÞÄ»k%Ì×pÐN3æMDð6°;‡Ä÷µÜYŽK»;&€óÒÀ±ËðÏ[ØÈ¡B‰ï˾¸zû+à~É0Žm +aý^Á9#îýyÁ« Ûi³½úŽÏŸ¯­ ƒéTªw”Œ¯Í¡¤5aJšJ +/õ£(ã¹Âê…Ú“*KÒÀæÈ<ç¹S +ÃçÙIXÙ>ñÈÆCA¸ùý)NÔÅk÷:². ƒCY?¢Vìïj\ÉóÞ*5Íf¥9™±0gž +ZM[Õ¥šy¡òôcM®o¼Ò-ú§ïWîM`€·ÐÖrF€åèMQ]#è[%Q垘”ë×JàWSÅÐñØíªätªþ,åÆu„±Ûa=d›z©ÈQê©r%|g4ásºÖ†dA6ëPÔæJ°< 3è±V¾ŒÉîà¯ÍbïX¾d¯ÞF_Es òëïÕâYhËؗÚÞ³â‘BŸFÏC;[¾þËX„)a8¤®«6öÖRZ­0W;]RaÐº^ֈŒÂ~¹ÛŸK¿ò‰Zº…f¥gŒžÆ “Ÿâ1Äv\÷#‚ãªçë^û<÷¥<Âëk)R`Z˜X± }*ӊ¸Û§8‚Ã"hY&ðTæ>K"…çÉS[JÏÐ$újÚ±0(GV•óÜ\›p•!Ì[8Ã"t¨äÇ_û>Oꖙ‚ +2‹†i}Õð%t~ÈèÂXŸ/_‡KèÎg„뎅µYؽ°?wÿÌw ï KGŸŸï8«ä·Ñs|ý½ú©5­çõ̗r%Éú¥÷ù”ÚÆjÝÌKÂËå’î-ðÏÐÑi%¹õÚªžCxJfŜ‡¡L>ï\^‡EO®šòLgx MÄuþΫeŒãˆ~Áý‡»1H ™‘ï¥ÔxaÓÆÆ ,ƈy´m¸³%„ÿÞ'^U·*³l†azNgEFœˆŒ8Q „ä¿/=Ø· ±§Ïºxs”µ"™e¾@Œd €h[?Fèö5ŸóõC'7ÿìKƽõ|ÑnËËw¯º®%»J&Ú S +ê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ŒêI'?YC…|¥m®c¡ÖD\ìR÷‹>†"©XG2QÝ;™Ã€ÛhÒbm:÷êàɱ}€Ey:eèfã½™ +» Ï]7J€%úTÊZ îݦ5GlVK³Ëmìè€SÙ]!•Šêð +¬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ËÌÆö¾·Û +åùlXWžm•(ûå/ÿGhS:kÆ2ðö¥ùhD(µ^v{(}Rx5:"º#0ȳ€ÐŽÁèÁ÷t>Zðsu›z V<µÖýþÜ-ú‰~¢ï‚æeJ–é[eùDՋILÏۚ5%3²œfÖt|èæ4¶‰R‡¢™»ÞA¼W ˆòÑmß;uœÏ‚¸.TˆÕN¢w ™ǯ·Ë|¬w‘ãe=¬ +gbKùÌEú¶$ÎéL¹—‹l~óÒ!‰ñÕ-ÔbhK–ZbÕˉL àL±@N0\;œ“=æ“=vNÝä«Á˜ò‹qbu?»ûyN†£‹¼-3|Q ËÊYÖؙU¥½4Äõ¶bRXÇ +Ùä=% í.rì„b:+ +2Fuw$±… 㽈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â†jU·Ê;¢O.yÅ8ÚZÍÞÆs7:ËÈí¡ƒÞ=Ó6žÏ +EùdµnQ±£mý£ œíHM짰.²¼*ˆeå,ªìÓmJÇá*pAŒ´+~5g`„¢KëÖ#¿âƒ²sÐÙÅ +˜Ng¡ýB£“Õ v—™Xæl‘ßO›Ç©³lǺØÚØØI!ö³š‡)L|z:í²²öEàJ› É°äÑwÌ»åT¨…O'W_f1|´¿éýّ…ÃSp›f UÎò®™v* +¯§=æÁ{©‚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ñÄèÄļXÃO)ºãÏpomLµZ䒁9šƒ…'ƒ1¼Í®ÄÂ+Œ¹NÕ<Ϛ§ŠoÀÖ1p=O1—r<Ë +- ·U—7“rz}-¦ð§a^…`ߤA@!@ Léßó|.•uQ]à²X—e=%l’ÇwÛÍ«t\db¤‰Rè‡Øz}E”« üšÙd‚ÐÛXî}Tý:•m,C™ ½‰ÐŸNè™O-ÔãYd£Å”VYŏ\ÎN!“»Z,ÉC¥¾C¥ñUé,ªlYŽ«Ò=)ã)DOЂŽ%qKŠBBZyËÝ>/¡±ž +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ÍÃ{ÛÒ=Ù®µÚQYÅY—Ξ%q +ú‚Ø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$2buP6ŠPGv°ö\¬É0´7Y{ŠP=£jAàmÆÄKˆÕaC‹MÉn—d\ж¤xðêæp¥a¥]»¿7Ò&‘¹ë7OqÊ} ±^ý䮬O` rJ¤ £8bfsµ° ÒáñGŠX‰±ő”bڊ$ç-V•fêºLjY{KŽ}ØQQ«Ú„X&~!ÇËzXU΢È61`ÊÙÚ}Ãk).®çÔáXʳæV0Ôn`!mƒ¸ۜm}‰†-€EvëZ˳RÁÚäÃ‡>’ƒ'Ï¢¸eî¯wݸ3< -©Âada¦åÿr_m«’Gð úæÅ ¼Ô5«êQ:òƒÍ16†µ×cÄٕoÌyµÿ½#/Õs¦+{ø͉UlOUeddf¤íª|-ñ,f03‡¤` +–îõÞçíÁÝhÜÈ]–NøWÇÐ| >eç-¸Ÿ@3”V•QŽoSUù*Ä2‚¶C³SËHÖÉF3‚%©ÏÛYþ™8‚º÷½kÜ×í”#—Íæу»ÔàÀòÑG—‰ˆ|¶ Î™òtñ#ˆÙÈ$ —‡‚¶áYxOŸºïÁdÆ6ÒRbR³Ê'í-`>$‰ 45~ ‚# ›^ 3ÀE;í.ù «¦¦¿>ʽ«OPŽövç»FwÝ0ápvÂ/fco–LiíÆOɊa +4‹{K±‚Õ,à˜t| ¢kʓʑæcS«øusá%ÍÉïî|ëzŽë༮mŠýdØ=ù¸–ÿͪ‘ÕtÎdÜuSLA= @êM±nE ¬îŠ–áÅ~_r5ȋPv& ®ÂÀ”Æ!çþɧâ'ðO~hÏ_~|†HZ³@k›.° ’GG -7 )('‰#k±Eä](Z©E[Ò6p–tÓuFœ®«Õ4¶2õ¼=`Õåÿ$W^ZÝüèz:Xó•9%ÿÔ¢àŠ”'¡<–íáµÔqQ¬´`Ê +9³S†-ee„bòtm?£\»rf£5‡×–T LL¸”7Û§Í¥ùy{7y^ž]IøÚ9u4ç+o“N4»13¶?¼½k¥XzÆ´Àï­ê@Ѥ‹.JÙ^>ŠND€9W38Üî^ì2<Ízse›¨ çKºh7ÿÌó-­YFÀ“e©t™äø6Œ¹-)™¨›='Ï'šðå³ÊlaöèË6®Û)q.ÅÇl9§0u0ºõlÀp@V®±6)W€ Ë¾M)K܅Í°hÒ°S¡ä Q†OîÌ{7 »éº"¬‹ƒ{1Ð¨6*ÓT7 ]Ú LOÆP´ +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Šá[´0åßÛĹG¶àº§v@ÒΑŒ’ãL(S0ñ”˜”%®¶Gbpeã +Ì㽟?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ìӍóÄѴ؀%ÍÀF}ÞsxçËÁ!/H ÏϲEñ 5˜ŸL¡\0·/ +¢  Q/n}nA°¦R4}€Ó°°CÕά4ÑDBçé1HY¬Sâ%µXöVÞ&Ån.>pç‘|ÏÖѯÄ)ŸèH9Ù½-À5(Ÿ•—ÊI]jÒÔ¢Nº¤ÆX—bbŠ‚í´=©r̤e°$©›W„` $Э`ÌpUO›Kòóö !^æÜ$»‚8‘΁­£A^‰S>QñI¦mâsɪ¥¡R{6¸æ¹†4˜ šŒÂÇèƒà/ƒxDIdQf¨")£ÂÁ‹="4…1ñB 0£ù2ˆ~j ÙÛtÕzÚfú¤÷âÛÜâLÿ’– »)t’íÊÂ׏#´…Û£E^ùö©©K×ÒjÍÜa¥$ AùD.£&¸rå©là ¯pÖ_,K1ù9Æ@ÖµŒA…C?Õߗ(aG$?kž+÷Ïۃ<9õ“ïêÄUÔÂÖÓÁŽ®‘«š=–6WÞ%pÂJžŽæªExnÕA¯Œjډç¯vEˆ#¤ 椑WuYóÜÔL]%¨ðªÈîVH’©Ê ¬EïŠK"[:K$!5÷hߖœ%£˜sCÛ2N•U ·õlÅ·²WÜ£~ ²¢n¼Ÿ©Ñ¯qH_†"°³eSÄ"”ÙVM¹âs„zï_×ȯÛ)K'Ìçw=L)΍ ׸<†bʆ]Ep?•Ùop,ê™ôÛ¤êÆõ±é짶¡lYÅ ®B6 ¡å¬”be~[È2G=¨JÑxÁ֗oº©Òl@{öaӃŒl¶æÝÑȉšåív á1vBnžªÂ±=Äùm¥Tõu˜nd ¼²1t›œô J€ó<€ýr˜ìf}v4œ%ì˜Üﶟ?ßܵù +×LúkØûnïðo,Aãs¿$+g‘(‹ÈŠw#‡HŒ%Šüö©êªóž-Bˆ¡¦¿súô¥ºzG´m¾˜Ù1[*Á +mõG"•SàîÉïáÙ¸‘îƒÄs8š¶¹€nÔe)d@JjA¥öfh¬4²›©úó‰Ê¾몮ZÏ!ŦÀÒäÓÖ&l¢,[×÷-áÚçÎ4md+yÊ cû"?Û+»Eß7ÌäzlÉÂQã«ù.Ô½ÀҖÁ×: !Ùlì`7e&0t¾ÀZŠÁ£I…ŽY—ºæ´)—§µû{¬w+JŒ`êç$«—éhÄì¨X1ö5"hÇ´Sufu!ûçT´É³ÓvVjx:;©DR¸µÊsäá󲍃!ÜmìcÛJ;`Æ õ›°2f¾¾LhÀ9VEÙîx\ÙìŠSN¡ ³;׋»`|J"8so…€+Ö¥#šCÀ¯7Üvw Û> åU˜UoÀ2ôü¿ÇŠXØäPTÁP·eJöãè^>ì,N5GAi(ZЍy©²ð¾*vNm³¸ 9ªÓî‚+ֈ°EíµiۓJÔՖ]-É9 cÁ¸`[W,'Ì®˜ü¬]Öt 7¦r36èb€³$òYƒÏX\Æxxë6Y¾2qyD÷–É„½ìË!\Ü€YÜ+N18®P»ëNm켂ã­n½™š*+Q ½Áõ³ô!pVS ᵶáà*‚È«_`‹$,£˜ß‚Ìu²”²/ë%r@°ÎeڍŠõ¹/h½v=:y×r]ÖMPØIÚu@2í!E]IØð±*à=U°‘¦ápÆ#‰ â³íyJkT}¿Ö)‚*$†õ'µÓKXg ¸ÜÓô,¢%õJÀ-ʝöPV®t9uÁ7Î(Û(ǽdZ‘«›TºÊ*?ß«mç¯"þþžàÉbZdË©ów£nâìíM™fáù€zN]C”‹‘À‘xNÝõ€KÍÇvçZE‹ìRšÁÒj"MQ™¡Ý€veŠµ +_zäÄtߑ„hêÉÚ (éYœû˜žŽ©ƒpŒq¦ó‰l”•×pŸ{þ¤r/n½¦;°`p’åá½?‡Ä S4m:2kKÅæë5Dhå?b·c°žË³•2#&6øÍ´ OÌ ÀFï.G|“#`7ù§oG9=?Ê5 +9›T‘qq=¹m´Ã~iKÜLix´+/¨Oâ¨ÊQÀÎgV 0TúT¤PÅEßC–¹N90Ì +Ô[N.³ž-Ã0ƒ»†8êµHð!‘š«0l¹zªAò‚xýÄói +副#zõ¢TrL šÄ k3û‰•¢LgDù°t—N¥­ÔÂ'õËI¸Ý‰@YÓKS‘¦#M'ã‹NØ;st8ÈÊ$í£-Yû¢®+ʉà ÷1œµ9+*•*5cÚϊ>4֚– aèNA¦øçúCý(‚D³DP[§×ÈRžª,ÊÔ=ÀCðÝù€¢ù‡P"¢m¬õå¤ESNrC8Tÿ‹f±+%qb¥¸ì^rVZ€ªùßÕ3ª ­.¹Ò¯ï3%†Øw%—„n‰¨RØôó€úpµÁXKµm¨yG¯ÒÂv N³ÇÎeãp*~°0z¼àÜaüæ§ï^¼ÿðýÛûo}xýþ·ÛŸ=ý–š9·•ŸÝ¾ûç‡÷o~¹=}ùòÅýýÇw?üúá5mŸÝþË?ã/6©ïc¾ÓëÜÕ$Übš`û‚ÖéR^TœsÙš_ j4©p÷gºcƊaÓяLÎ˔߆‡àP ìñ‘©´³¤¯²T™tÈôËïØ#ß¿Vlþð*üÀlm¡1zc¾³"BûNqEµ¤‚+½—Ï(ŽRÜéùÂÈk6LÑèàV“ʦj7¶5Ï8æݽü\ÕKVaL|Վžˆ™Ñ Vxf#ÜŒçÕì*5ÃG-q]ÎMïŸZ×ÈI §–ä°TJ;'×ï/Ú JfP×bƒaá÷Ï^|מ!ã~9õ8÷_)Á*ܺ—Ù=Äbd7š?tX*¶/"Ò°»ôn°X ¸¯rfœÍµ„Z ò[⦊êhñüÖ3§°Ýƒœ`9Êöx+ܪ‚[ÚçÌ®j—$ÆÌè·Ê +÷£áLŽ™ôEÌó(։SB‹Ë#áµ-Þ>;@ùíõsBX!Ð&úËÜ؊án¢‚ÿµ_Úo,sb_Ý}Z]·È$Å#ôð±­âO„}õ~@“¶$.i'ˆ•5ÑٓðÄ9ÊY=óÉÓâA8ßSºïq`q5x “Ü`¢”->w]ŠU2‡nõU¨Êz¸>]÷ó-ñ‚«Cv›¶msö“}„n6m@çÖ|PËU@Ù£¢ÅâY»~<§æ¥Íj/ —šrãá÷9a—⏓BAâÌâû£|ÑDP4Ófc•á¯¹1µâþÕës‚|š=ó(F´!À–T[é'Œò­‚{´±. ˆÜViØj1 wþ¸V½:£Ì +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ûòâ±åã§}Ž{ӆÃ䧃Èö)JKGgú¬ ‡ª×°©•I³öj²ê ÄϏGë6pÿotfG+º ¶biƒß×,a«…ûÏ ª¶$¢¬ÝÒ½B‡ÃªÕ{A„V}õü¤ócdËö9£M†·R¡$04¨26a âÅDïݾ' RbÁ•>ßÎÃHߝYã<(½Ð;ÈÆN^Ý;ær4<ý ‘ukàÒÄqË(Fhšvm4Ìc9œ©2ÍÏnã> stream +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êà·o ´ƒa‹3$fµ5.÷jŒ +“ª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î­#õÔ< ¿ùîóQ ,Öú%~¯W¡Rdm¯ðäp×ç˜ßè}ŒÓÃÝfSWúÍö$͈µ‹tÑ*C0„’Ø×ohûÕy>ŒÑF×¾{qÞ"ÖÚ6ü €ì³Ù÷ÂøEʙÚL]{¼{’œ >çq"` rÚ.er죋A´Q<˜QÖN‡Œ.7 +9·Þ½À£ɬz êa"‡°6SC©ðcÑ30‰±™}¯ìŸ +Œ9¤¥KÆR]´ƒÎØxz +` J¨ÎíuÖ2p§Þ*xOô¼CJmÁN-f©`ÙJÂô9àK•ž¹©Ð$ïLV¾G©,€½¬/üpý:ÔH02{.VB¢'ñqJ ùÝX5Â9NÇfdÄö(Ëe§EßY¼Õ +¿áò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áØG·c¬Ø¼”4sÈþ‚!çܯ¹ C?½ÙÌUc?56+¶çÀÖ(4Âs`-&vòpbÐ-ë9U>É^'£§½™ð%âÊ6:‚¶/¨½(:biÞ;TÖBOÁºz¦JÍ>„¯¦^¦j>~Ériñ ¸E}êðvBž ÇüCgdk>K‚à èÉ;þ•6/ÛpØ{‚Ô®&h¥¯åO$†ñ]—_ځG'á5êñ4ñ®vìœÝÇaî/ ½ÁpÜ2 rop Ê;‹CœûµÌ}¿l¯b¬;makœÌ.O.x“öʁ¼êÈ)^B1m¡qÈ$¤-€3Vëˆ¯ƒ=·tmҩ˛—ØÆVéNÜ}1YÆ­æ»ÖÅR<ý¹a=X9ð7·ÃR|Ô;ž\Æ·ì9&­9w;½’ó-Þç½'¥‚ÂóÓo§C0~-; ÆÝÆÊç)q÷ƒœ’wÛLeªc€º[Þ9d8A0Y3t±v€qÑß²=¿V³ùîK陴¿ÐÔdS„ùhSzߐSËå=|w“™¥nµ}á4£Ö¼µ… ö|¼%,áǨ†{Yۏa®%BP™+¬¶H&$¼È1X!©Ú-Gқ;äÁûYcw,Ï°E_гW't£ó“Ÿ¼ß砘ŸßoÒvT"òcÓz#!¡œsQDœ1ÙÎ9m£«!ýiøUȬv¼¦­{'¸w'â6c»õ'´ƒ¿½Žè=­ù£{L¿ÌŠPónfï `0‰¶¤^«Ï÷ªÓU*Æ®J>¿DÀs`­xPL»¿µ¢©–uk†fïíkΓi+ûÅ@ØÏ9ýG—þÙØ |zà#6FÏÐ?¯wjY¨69Ú¯*¬Ï˜×~d[+ ß&%• +„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™å +Eš¦"oí¾<ösвƒûÜ}•‚’O]¶,-ؙauœã©TãªÎ',)¦Ôõ­í=j'vôÉiNv7nqÜ[Ãb‰M7JÐþÇA½©wX©ÁöžªÂ>t¼•çöÕçKN¦ÓW6TŸ›o⓼Æäiôqçú¬h™¾^¶P!#X+W¨ÓAÖ¿),Xô˜£–KA3::ZäÚ®µtʘdn•X)ÖÕ.W¿ûÑE).} òjÁg×ڔî+ ¦“¸K;mÒA‡`} ié=û©¹ +j7 þÿºeáV¢}~ÍÁÌ¿ù®–äÈnv•¹@¦D‰ú­eß"UÙùþۀ" ×ŽìÜ°Eñ‚T áë:+‚Ý;¯¾Ïe‘·]ó ~+ šª›@©£’Õšc(1‰>7ÏöÖ¸kù΍ Àítë¨á4{穂pÌÙÈõmÜÈÖP* Ì0 ûú©'ÌvU®,±Á†¤3Õ{4¤õ+Í­eÜf¤äÎ¥2*õ/ݬv¹ÃÂll}=î‚4L’gs! ~KáQw{‹Q€PT{œ0ŸŸVŒBùæjðÄä™3uñ¤Èg8­®‡¦X¬Á¿NOÉôáԚ;kÖý~¾Ì­«U~î­T…$Ÿ°õ2Dþ÷k”__,À9› ÌÁ|À3+£D»¤jÈèÍ$r¾(¥TP~¨î|ýîg +÷k^_ö «†æʎÄi¸Í¥êVQeÏTKJªƒï¬\öz¦i²Ç²ç(xĶ0]„1Ðï÷RA¨Ðs¶‹)ã]–'ãw´·>ÇIxÆtÉWÁ¹ž©MöՅ}Ú¹m6[zUÓÑU©Ýå©3رÌ4v +`-§Á©3ïò+AVZ…UC!Ø7y w +‹ÀÜ£Šf®ª}/µPRGøêJöèÅï³êž®Êæ`399ã9«âþ®´Ô؀d #Ý,Xˆ]Åõ À˜µ(ÛgDf·˜ÔR¤«ßڎõöEøq»›º„g‹7UæÓàÉv¹aô£ŽŽF20*ýÂ[/¶Ž² Ýr(4ÇؓfOs$PE„ÐvIb mÒź¤Œö]yւ«_„«MÂs?èdÑ®+P[HÆ}nC§´&.é-Ö6l@÷2lc›Gw.[îÂçŠ`ÎI¾ÖÀ‰Ï  W‘&J–+Jã2ïAûLÆÁ¾’òÏ&¥©AÂöÂ÷ÜRâ.&­ó­¾Gaf0Ý6kSkÀn8«ž†ð<š#¼*uSÜÔT—ñ¨Ù%NkˆFtèÑìG­RwT¡’ú¾Ÿ§ŽmQ¡Ëî÷fîHÕÚg¹€FeID¬ÛƸªT9¯¬àŸ?¸ +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¶×á<*ýý•¥øokàiPóâÙ'*gUKB@‰qŸ×ÀÃü¥VV­ß]+®K­èo[SЬçÒA’_·êÑa#É´£¸kÊʞÂ*@Ó¤ Ħw¾¬ܯU­mÉ8)3R€]‘ýnw¹YŒš'èYZ!4‘zl£I²ý”狨BÛ1TÁ<‚S°4¦2éç·çîp~£!àꦗ¶€¼¾Të< àåeæ¦àl|’m‰Àû¾Ïêþ›sÏÁ3®û¡•=Áb9?ÇQ›ƒ°|Çg‹ßÛÈ +Ö}-ÝF#+üž2 ­Ç\+ÎMÙ,=Çöu u¦zÎ\lžå€G óª1XÂÕ[ýæA4ñâÑÑ+Ò¡¼YTí-³iš´ØW2§¨†±س>&¸Lôd=™¬ãÏ-ÎÄ»•Ùӏk¼Y!³‡€¥—ÕÂÖïjÐBäɎæãø‰÷LŽœ^ÅÝmÍîi¯*y # .J» ÛÜ{:¤È¬7‘·‘Ö[}îÑóìNb=gOR„>g}ÖYÞ}ÉqD…¤c#ö›ŠNŠ0€ø“ î§ìF(Cãýón8ÙY¶!Êë/a&%8q}oYu1…úŸ—y^UÙ!è)Ëï´+ ß7iÀe +žÚ4°|µH“2 Sþ ZÀÕÏj&ܶ·F×õ@ëùÀ4ÐPƒý¼J栙šéρ8­ËG}^Æ&—V5{ª³„ñ龓ƒ§Æ‘Å5#vØ?@¨³ÕHòTÀ½ÊÝ͍l@‹õÆÛÌM0ÄՉYœG+»! @3Q;/š("7™Œ…ê%xøT³õ,ÎxyÍAðPF֖{0ÝßêÈ:X‹ûS}Î÷ÅZxRÕØ«Z]Ä°‘—ÖŬ?³ý¿íž#Oï¹iâ’Ro¿~ò5Þ`¿þ<곅ŠÓfÖÍO®íQС._„ëÔÖ¦¶ ®ŽyÐÊâ÷5hEÛÂÉÍèc€ !X¸/|ó+œ.ét\î³}ڞ菔pG'—‡±CԄ-Úëõ³‘S Á²¦þýüIøÍz‘< +8Dè1ۅé\Úw}௶_O.МQÉÔs3Ï~oNŸ¯ÚÃ|SËϦ‘4võKl7 Tp_Ö}Á¯ˆÊÐêeƒgý ؓãz2ÀË÷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¨‚Ox¯£xºßàžÂPæÈùD¸–t ¥cíÒðù¡ƒ*{[kÞì´mØfŸtq]¬uӓ kœ-€Y»> +GèÞ§®Tc¯Í³$W"Ålžî¹3ƒÕ‡n€yue;ˆP K95õDÌe?,”·‡e]N»¾U¶k¦²Ë+)€Y h«íMwôÒ@z&À2!(¨ÎX..¢~X/õò0µ7¥Ñ¡1ÁjÕÉ´c9í]½’õ€Ý5ê{¿Â¯}ܼ«9ý’°¡,n8‡ì½®)ÃŽ-@]5î=àìÎ<'×Oü¼·¶T¢iÑ´¤Æ¢[%fÕÝñ7WêÀôà,¸áe6]#¯qEÀcn¡êÁ‘¯á`ªm;|· +š^-1$iw4& ¯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’Ÿ9 8߉†2ßÒ!çÄ|Þ=í€˧¿© +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¢X±d•Fã"©tï‚Òœ–v„P¡^ /BcݼlJ}¦>h‹éÝ$I7ƒ˜GÒf&¿—p:ƒ*=²úÀOʙQ]¡>1€~¾ŸAkŽùÚ` …i¦}dcvý:©ÊêÐA½Û ©öϟn«Æ[•ËÉɈ«ÎÕûˆåµ_(›1vZ‹Þ暌 ìA~ý’}XiÓæìŠÇàºxFppÒ8u«/·37ËÑð`…Áaúk–WN'xŨ­ü~Âæ`£ûóo¸a ©D¼K¢pX +¯Ah ùßûÃNZ,¬ÕBžÞÌv ÁÃÊG,¡~Ž!w ¿­[&¸*³‹¢˜ÇíÀk¾J/=ÔH£EÇíðJäÆW­±ZCvuK Z-8ƒWµç›¬Ñ·™úžÏÛøñ2m?øœÔËA•ƒ¶èóî< Šš°%•Ó0¨cöë:ìQ(®ÊÅîöpJR_Dgܾ]i½Eãô€gÑRæjɌkoŽn“ŒÔúƵ×ø/¿9 +9*™JìfÄ5 Ñ^¬éê«Rˆõ\Á¡¯M,ä–CË]««ŸÌemq>Ÿϔtø]sÃíE0yÁ›´M׏™ÚæSì½’`6L*•ïXÕ~ºªõËÃóšxÄ讨9dÿE°í[Ž•ÐÁÞó-Áۊ[nVא@5TÁèL]”ŒbuÐÔn÷‘ÍJ¿Ý™,‘Íge2cÈ^ëµhœ¡ãb\+Úè3¦1@V۔Ó7ç3•]0:w.gí¢ue¢Í¢NŠo'Ä,—J,‰-ʃ›f©g[&ۚmIp1Ú·çÖmPêìTÂƔvU\Cjt´Z>lg¥_k1œNfªÏ‰íaJdÒ9®õƶ\–ì…rà°¬ãPÖ¡©ºO4e9÷0ö§LkcSetŠ–1?(qm2‹ÄL¹ØåaœÌmůMA__W›Ep̚êkC—mŠYã`TÉ[}=ɁŠ¦;4”qòýùO'°‘{U¶D„Õ÷‘Å%¦» +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Ó»@l[ÙßmX|Ëèƒ?ªiÛn}³á½Ü…mvY³ÁÔú¢×¹’ßÕכ;î†ÑÛÖJ—ÛAµØ/¯2eN®ÖÎ׳nSoç>±ËøX)Ÿo ÿý»HJþ?ïU¯#ëmCŸÀï0MÖ%RT\Ùër«äc‹àk7EÞ>‡?úþfÜͺ°±÷ ŏ¢ÈÃCco?O£÷ËõÑU½·Û鞌Ÿsœ³*% @ÅÈ0jC­Cý¡d€žvðõô~@«D¬|Ænᓚ,°åç;¤䈉1ú\¦;H ˆø~®JÀƒ²þP 6>Ýk)m&ØK™0"ùUá …Xµ88b +°TB–ÏËA/^klR{öÛDUî ùSÌÝÏAµ3ùضþQ¿3-5yÞ(,b%[A8m•½Ö$:„d;°„ÜÜLž3e VÌbJÐתE% Ç\(1hý[h”z9owÉ'0MïbSIçæ¹Kâ8M©só.",CëÁé:ÏcäeE_l1Q˜ùâ?ÛU—:´ÕŠ `~üh[˜c¾4&ØÑƌfg©+À(ÏGDÕÉ{Џ,Ÿ£†H Jó<‡zªAîئ4cɬ"?ÀfSÖ-ÙVp€v-&Õ63Z=­0¥š4]'k4I­[¦_ëx$ˆcpKÀ‰,n/°ÛN¹‘€¡§ê¥H£|Ì~‘¬´Úcجu}˟ß ÚH¶ôs« ·–¶Ämèªþü©]ê;\§·ô>½l—WÌ£:—W(Òà¢ðcV \úìçëÃg§Ëóc,˜Z\ÝW(浯jšZ›×ï°íJ9—º†Š-¼‘"Œµå¬` µ… €¬ÝJ°w¨Ô¿ýûǟ¿~ûõËû·/üþÛ×ÿÝþè{PiǾÙ~¸ýø¯o_¿üþŸÛ÷¿üòóûû?þùÇ·ßÌô‡ÛßaøþK¡W ôðÿ—¶+ӓÀÍ**vɾ:šޞwõ¸ë1ê2ÐôØ㩄kŠfÀ€¤äÊôh‹B˜êªÀ̾zæÊ QI°iÕô:cq0-#s9Åj€#ŽB+…B<¨ÄOÊ)oTK +×|ˆ• ÛE¸Ü%à¯`Rl©¬rɺÅæéù¯ÖY¶<­š-û½/ÕêY]e1h½•?öÕvHŒX€Ìš ð¤íU[ãyˆ9@Ò¢—ZDû*àO¼}wv± Z³uE›Ê÷þfOzïˆy´C ËXqøUÞ&Ú"!¦¼öì×{@9J9öÝëwWø`»¾u/q=ñÎ(IrW)€[¿ôïÑ´¯÷Ø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¶’Ã,ØÒEEá@fé RÔ×Ä˱:Ö ¢`Ä:7V¨Ûæ$[ +$®‚öZ3‚ŠV½A-Zƒl.ÈöM¿A… ›…Ãëê:eG}Ï]nÓi«‰•Qˆ´´¸*ª"íï™Âë<4Ê y%fO!®RúÌ7Pqí‹ +Qðö†LZž(ÓÀփï*èýÓ›³{3> +ÙQ¯“·Ö¹y+3ƒ&•zúM^ûýtÅ?Q¾cœoRLSÜdr 7SD?gOŒ{J¶j\¤Úʗ¦ôbŠ—’âwŸÿ|½m„Hŏu¹„K43`1ÆÌQp÷¢ •½¸[]w†\w¶pjdµ_ðaŸÎ³§w:ܹ!âÃUÞ[?o¤5A")Û¦å@-³=7ñÙ¶³³aúR¥&aβ6‰’˜A-àF*œ¶Ügڂ}„ÊÐ~ ñu¾)' Ž95M ·ç©s=•„ýšñWÑ’#7ÕäF,$mÄ¥0R·Ã 5hÒa–áÍÕ"K‚ŠEå–h¾lM¡Åøð[rh ŒÂVóc±Íæè5ÿOYûHÑCd«Æ ŠÆ Þ[?Q6íŸÜk  ¯ˆþÁJTÄQD­U¼Bf~纓×'®MóEG¼)<ÛC}$,[²E ˜ûª•k, eUyÍ8*ÍÃÍ^óølœ 0Ú7Îce[€&(êKݚ@áó0ö:À¤LiËU[€³ë‹Ó7œ¢·(6 ð«Ó7.[l—2P_´DZi£8´ªd ”Ѳçkьk LÜU² C½H:€a&[[RŽ™†l´çnq)Þ^uD ËMéô‚hߎe‰;«/°¨*æè×ÍJÿÊôÖ4À”õÙѨզ-._s÷ƒáÿy¯šVKŽú Þ¸›€½È¥¾UZÚC 7&Læ͐/î[86&ÿ>§JRuUwÛ«öcæ½3j•J%•b‹*œFOsYK ©j¿Ã‚-w\óD–;–}‡ermm#tãªbÑU¥«ØÈüU¿'QX-;ì|OýāHr,½ÎSß#~Ë3þߧœÕ4£vãÛ4${÷Ëx;³Ýø¾M„ ¶M§[#E'·£ÄÒnÜ>@†!$VHꬉ‘­ OX¬z C,båê…îî%6]¶ˆ)¤}Dn‰) +6]H£ª¥„µÿå*[ÿÚtBaùª—¼›XMBë`EøEâŸBmÂAãÏ!ªOßf³€"°%Jö=¦$ŽŒv9ê ÐsŽ+%4eà£VàÔ»hÍ5i¨ªÅUZˆ¤¹¿mwýÆCªÛÛÀe/œ‡Û2ÂÌPŽ7i3k–•Äa½БñƒÞ¹NKa¨¶ú‘$„d+US2õQd(›˜gùšÀ,º ûÑ\H”—ÚìUšÔ¶³Îοîj%K —È©L\`ìy=ÅKî«z)³lKc•fÄS¯:§€b9𪓖llÖ5‘€ºp,¨ku€Ï¢ºu=3@ˆ„!à'‰„Æרü8Ü«ƒh*mÈUmS¬Úö¥Q„€5kû\͆µÏ~ѤÀt-˜hã˜À‹ÇÝäÏÚO÷¨¿}üŸe7’Þ$d4›½šwiåņe×6ºdӂÛ^òáe…õÕz‘C]*:;î1ˆiN ¹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€SiÀ›“ŸÕÉUZ±²îˆø4{›Šè͘ҤÔB£ Y·AĈ6¢_ý^ó/x­aS»ˆ…ŸuŠÉ­W:0âÕá6q´ ±sÞCK¸:²`ÉYL™f†?‰ýºe æ,[Zæp}†{œ_úhç3‘£†òܱ ¶Dړk'#›XˆÛÀò7nœÙÝL§ÔÎ)ªŠGc{îP½È¬C#h± +.n•š‹ß%g.ß©ü×ã/¬¶{u&ÚÕJJ‡ÐLÒ`¤Û¤œ ûL_¤Ó$=¶¶^ÀÍãT •ÂZ PhÞiu(’Ûið—¶áNXšÜ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äàZûœRØ7(Ýs–õ`q!îɯ¶ëT?Á…Cë鱊&5XÔêĪ-„P~éýûAµó"ú™-Üíá&ÚµXÁÅR6ò„[ +;§lL×G»—lª†ê ùL(ÀÉ瞏–l-°1âFi?öÃìZÐ +´Ëál[ -wE&2:Öâ" +4±ßš‚žáº–œ–·}ϑÅŽŒÏ쭔Žö}Ðãá=£ÖÖµ4uÚ_× Ü"ø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¤Èù^Y:*6b™b{îôÃHó™ñ…åÒg¤C½Ìä?Jc-ù ¡û&nl4ºv­Hb°$yqš¶ ä×¥-‚MeŠØr_r™yé±7>G»‹K5öÙ#„GÌ•<&Þ5¦Àì¬ßàͣ“÷Ø º¶¡I|=°æpÜ;xçäWÏxÅER¢iwx*œœ[s¸·æäÎ`]@.Û +#½ÌӑC؄ªÛ†«Œ]ä8²‚[›Ý^F{M§” u™îTQ¸O…-K CjªÆRî•ñ‚YÔ¦º8õ{U§t—0:=É2P©ÃØ ²!À;/ì?Ev€Ñ§,`ÌYÇSBš)t>ªm(¶HD,}«J˜Cmê–Ñ¡ö_»²À9ľýL•¬¹|а³J.À‘8¬Ñb¿¨µç×u$û ,Éû `ÁÞªø;9nÏе¡ÜäZÑ^ÂÝ]áx;Káëuêç% ×W}Jw;™ãu0ždžÇ{ê5‚ëf’ݘ–×@Ñážk^ßr€rICÂAE†YÊ÷î^’}ϲÄKTót”€Ñëñ©äíóàœ†ï¨3-rú²xV©+LÜ?¹‡ö±X!)ä¶ëCËÉc’wÑ»|ó·/¾¼}ü ~j·ÌÚÿùôR†Áfú›¯~_ÿþ»·O_ÿãÿù§ï~øáó÷o~ýùÿz[à/¾y{ûîùùӍ¿„˯ð÷ãO/?âÏBãîöüðo@?ÝÒ폷oÿænŸšíŸ_~K rׄ[ ­@‹0¿š9vm!Gã|·¨g*®b-qÁ 2 &NÅ ê˜k!‰F`)°à<esB™ÆêX±íÑ_šX0 ¶à>•çUj£¬ÚÊàLÑË©7_yÖæèªÿ]yãn¤ïÔ8‰(½:PÑ,SÝ8€SÃtìpŽL40Jˆ×¦‘){Ø?·G«»^]0Úx [†f‚9§Ë©Í;SPZIR*øчÎeN´Ÿ–4©1I&(*ôCÕeÁ‡Î˜,qAƒLÞÓà£Æü…Éֆ¤û.Ê0Õ²]ßí€êáùƂµÐ¡no£ï0÷zžÆ¾×D›åÅ'Ê͖¸GÝë²e¨¨Ø¸UèÞÚ"{ïyàÿáݖ¢tx"[#±G=Ê-Ñ93ëØÞ-ÛݸF”[§Á‹?üo[ŽÇt³îk2…±V +À˜ÛÝÖ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¸)½kºŽç%ŒEê>ŸÙì,}R¥±ï:Ø:µ2÷j§d/‘ +F°;XË Ám +->]NÝýQC™Ä§Ü0Q1A [¹Ê™­\lVk[µGlѦÏÑFB”£XÛ7ÝÛ*À֏óÛOœñ~n¡“w‰kF p—©ì‚²•bæKX’’ËåÔúØÀ«ï¼_ëÞÀÕǨÚ½áëÈ9rAÍޙl…b~¤Ì +°4´MÒ(Y5o×öBPu'hjû¼·z{¨Æ;sŸ^=×@̉Té.ô‘Ìã)*ÐÖCTÑuÖDl¶‚Ö§ü8ª¾¥¢Þ©¦–f<%…`n…J Ä}AnmÃC·lQ¶r€s÷hpË @|Zx:…¥gj[Tj\Ðw¾JÚ +$m<¯’EAç(ڝN-P§Q×bJÉRéбP܎ š?ô|•6¢kÍP¿5 Ž’ ö<"ûæ€9â|ÎR9 +«dG×-QXYƒV›hŽ¤J­F¬Ž`_çï·í©4·§Q¤ý^>ž¤Î€uÄ$EW˜¯8Éq¾3WÐ`uv»×®™”¤ŠHÅa¼ÚW0œz= ,HÀhÚ3Î[~Së–秤°j$Wµ!é*í·’wLYèùu×_CÖÄû½´JÙÑ7aµNãàœ±O`Í}Áì9_ÝK°qïl‚y6¼/ ûzgûQ”x¼Tf ¬Å 둽`JÛ§ªtØO.dè‡6‰w06B{8}{z+jIÓÃՓ&Ù8ŠŽŠá ÀfP~¸¿QvÙç4üüegI±2@ù%;q›Dc«s,˜/+ ¾5¶hç^jZ¥AegÙ¹Šlv²W“¥_m^SûØîŠb\ù\ôôÒG8Qö̪GŽEg—Íòì}Qdž£VÊë|62¸ç"¯ã-0†[ë¬Và$Z§q}̗{¹õÍÍ£yxš±d·ñªÑ ûEin¢¼ýUf§d)XAWÀ³>ëâs ß²?VÐr“¾ð!ô˜"ÄÊ(¾ùΚ1҄p]®­¨dñ–ž4Û*ï +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&+üe@c)+}êšHËČKÉó'£S‹[mðz…–­†õT-1ëZÄÖ¥9q¹…ºÌ]í‹Ð.´9D±V_ca¿Ýîõ3}=X`¡1^²¯K‹9<<£,»uÌ`7kÏ«¶ÅíñS±ì8\ƒ ù|#³óCzTµ/šQߙ +@Ù\h×z•ó­äå€ä[Uµ:¸8×1‡£Ü? [ë¥g[çyxZy'rd““§QŠÁíF]©½ž½¤“­´ž© }µÎƒm¥65%ÑP‘`VÚ ß¾4ýÚ׀PIû^aÆ\Ô%QkÅ$×ÙìBåmÝù:ÐѼ|–äً’òއ]‰S͜T•ÏËaϵóXê l&Û+)Z™±ŽðñÙ>ý9NÞAÓçó4¨Jµþuứ“ã§Oœ«r¢ö«›tùáýßÐ=¥ç뷈\)Ð[>Ü»õݧ_½¯Ys͎w¹² µokÐ=›ÎÑ}úÕ5ß6âO`Î'o£Ø,ÄW0k dn\©õÕ»M'Mî| ú`Š>Xº‚^øºaC&‰µåbµv4'áॿbÚ%î—àëVµAáOa[j„tøV¡¾~Üòt”z;ŒÂKN÷‹QL_-lU.)=˜åCxi»ŠnFë2÷€Ô: ®ŠßÏÏùÚ«d×®«ˆ~ÿáN œÝ70²@HwĘ¡ðAd?Û +¶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`ìVRg: +{)Ä«·çú°¦ÃEгV Tkus‰£jó/‘Ï82JÝUwlÇÖ¬€&ºÒâ`W_ac;uRx”ªÆlzu ûjÀÐਓz÷Å${Â,¥Æ‹Z1i¥[¼­…r²Y­*µvr#¸qہ0d‘¬ì†ó}Õd#8’Š…­#0;ßØÜÛqY…õ¼â§;µ©ÇÇ¢$¿´TZ3~ B2­Þ½áyOOwz8]£¥™³·˜®:½Œ¢Ó%íˆ.ŒfËiÓ9hiò1]’õŽmb¦Ë;Ɵ•ӕÇq@+¦K°…»0¾3¦«GÇ 9]y8nW…Êv7­"§«M¯7·Ê§«÷Kkfòý¶¼¼·£-µ±—4 ÂÎÿK´œòÿ$[LàU8#‹l7ÂuXø|Î=©Ú¿Œ\ýô½‹–uÒ«Þâï?¿b½÷)±Y•a°Â:ý áÖ Pÿà Pèæ"@¡ÿá­`;á*I‡Á¤…Ãp€¶¹y’(v-€í¤Æ“c.L»2ˆ¶[.í,àӀ“±ü&Þ¢ßoäÒâӒš¼¡zø]«‰5ÒÒ0#þÞÁ¦iÉUÁR¼æö¹+K‰{•A•m–®ÄÄPVüfU^+@Ëö-ܔ|"?ÙR‰Å@…møïs&‡²“Âå t: :gg/PK-zç"{ÙüC+ÎBÐà‹Ë¬ZøQ™‡ɵ5ÎBqÜer=»á%SÎr©ØCü˜1¨«\ƒË5+¼æ —ˆ/s7C™8JgŽi¸ˆÂÌ*ð£vÛ]šÀu=•†nú·«‰Ytf°‡&‡¿0]†á(â(ͱ£Øêø- ”ë{;v¼¼v”¡Mü牊Æ_–ÏÖN?{“Æ2ôˍ¼åäº4!‚hN+ëô—Wêý¨Ÿø õlO`†µó®Lf1PZOì=ëܕ}¤•µ0ºB`}Û_K^à))Õcæá$ú.aêBÓû0͑rµ=Iêë!Ä@[*IÜ9ÓòûÁ*ú +¥B´ê­Cðk_6[ȍDIEˆa$)2ôs¼[ÁpúÀÿ%ÁM}»ó$`#Ó¶ O÷ïjDø éœ=L+À`Š÷/"ÕРÛ0H!7«‚W«*¥6l)¨žVêJǚŽ6#6g5œ",Ç ›L`hÞ¾¥vèþ­ÀãR>½úÇ«_ýý›7?}úÏOŸ~üðþ»Ÿ~>ý¡túæÛ÷ŸN_½ù6§ÓӇç>¿ÿþôï}÷ñ‡Óó‡ïøúôk|õ;üwlÔ¨…Ê(f£RP5·fЋÒaÅʇfËCÔÆ¢BHÙf£¨ef ë"kh³€uµš\j$6Ù•Š5.Zƒœ}«!n¡2ەcåú0ý‚oíö¿ª”¢6j+‡ÓNÍԋË(Sñ›°ª`A²À‚iȉ™¬Ý%7k‚[£&=küqפ„#ÊbüºOÊ4ì¤n+»›(~@±“®Q®,ŽäcÑËn[Ml_*ô±vé +¬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`ÇÄ Ú‹ñéQÆ 7Mkvc%—à€Z|ٍDÙÐ#¢Å@”j¡ˆÚn-ÎVX¸€Ëü÷`çlÆßvpCZ< éÁ1Íà€ÝzpnèE†ÛNñ‚9 „(¼h»<Ø´òMê KapF*q™;ðw*3ž.R<æߞ¼e©/t™Ò†Å¯¨,ÉÇ`%8`BÑE”´ò<ð…éV}K[!fÜ¥°à\a&.ãnŽ{Ç´b”OGøéñdkäI±h@—’g§cì¥ d,ޖ BEÌl™@Þ"Cj”1ª3\ëñ`²ý(esu‹‡F§ž«êžB®dÐ4´\•Æ>å¬h­¡² ܖ4s)b€þë†v­,ŠžMn1 Ë!ħª…Œr§îÌ°ŸÖA‘Š¼EÆÌNº-„§ ±Ø‹”ô¨Ç}ȚÓUeÓöšê©Ð¹Yž,4_ú°úO›Âný2&3ݸÌÏÐo>~ÁlBeàG–³=Ì+‰¯‘ÍómHÝWvòe$~z~|¢|§½ŽæœÒI¯P\r›V‡¦:ºü£¬è`ˆx‹ð6©BØ‘¤…M™GÎ(¬ ;¢ ÃÃX…ˆJE“Ìè‡Î~ÎT¶¬zøÅëÎhègʆÆT2n=DÃXZŒ8èÝÁ|–]%­øwì.:é[JöàŒ ]婅a‹Â­òüKh•¯Ê®pnj›É» ‡65L*ǹATÒËôwÜ¥á˅„¢äÇÀTþëÙO6^“­Âß~t²ý36°Ö—G«O͚ `‹†4ûͻǷù3F·—âuÀ§Ú"ûE&E‰”#iR‚×›9͚ẁw·C ‰<9÷aÔbÆã0,ْ+œÍݏ¦Þ¼L«¿O_}}úÛ_©èËä´¾–mìÛ»ÅccvL Ù®M ¸D+©ªPš­Ë[¶ÃÂ2)}óëcr„åB©êð,cÎ¥Ž'_s3mN‹hÍևÐ÷zL«­Å}´o1ÜÎ9ó]7U¦ä£Å#tŸ±üÕ`çž<¼[¼zpJìՅg_u_ù¢qQVŸ+´Â,Bm:Ü@ Õf Ù½˜•"©ÂðŒ6À@´eƒ…0>Gx¬šµ©sôKØ&ʊE1Ñދöx8†èªÞ7aÑîwi2œz}ö$™=Ñ|Žß1~ؽ»^ òÙǬçR4àõÀHÂ,ûöXtǐtɨcý7}Blº#‹vðÝÃ'Ä¢Oí½Ô­kz.µÞV³ÂQÎø:]ž|)€$vW9̂ º¬u ÌêWÙ=¿V.p:è^@/ʶrŠ48B«Æ/’Vçˆù%fÌM×ßq8¹ê 0rc2¯ëÜNäܧ"¶ÙV°³ãŽT,Ÿe;Âmí«©<{ÁÖ^íAX3¿ÙîeB<ú|ƒ¿ŒHÊ7"é±:z¨‹B×TTè !k,z¾‰®ÜꤑË/ߛd“BÑóÜ_óüàç¯xü”ÏÆyÞ:{™%-ðF@±q.E tjvDüLÆî_Çç‡'ÛÝw S•Ui8=hwßë#Ü ®f÷±™dX£ö/„ãóã³ù‚|úggrÔ+¥ üA¯Ã’u %°•å.ˆ"÷0¹Waî]6;xu*åó6võ>9ãÕõ_ê«%NJž€;¼´â|œôÁVp„·1âþ+Êv:qÒi„†Ûêı«»Š÷µ"1Ãu‚KvÙΨ®®Ï®îƇÈ67r.x)XÔëYÏ ì~a'GÆC;7rDÜrÞRëàNŽ[; vQ¸f0a—íˆêëºqðš3S*žf©’× èà'È]s Om®ŒÕ<3XÈaQH¥mkã!oQ¸f0a—íˆêëºqp#纽¿¶Ôæ¥v©ÍKí@©oèñeÁ„´èñC÷v1‘GW´lѾ¿U鿧ê:ÑQÅÿ„ªü·â ý<§Hö~¸Pï3_[ᯠL%Á=q#È+° ò¢ò³é©ÎßύM‘#èü^Î #sg/C+ é´-d¼êÓ°t´d= —(Nös@wýôÁÖâ…7a*²-…ƒe2Ô`Ž‘‚øÌ¢’¨A®Š°ý°!ËØ/*©FÃ"7î`@â|†r¿ö7ç(aQƒ¶gˆêx +y-ÁXdݏ^«(D7¸˜ kkIÌWR˜Ž¢& /êÚ +¿‚ +Š_«ԓ,ÆÊY„—Z-.@\E&ª€‹ˆt¨™Õ 1ö¨YÞ¿ÔÚbU® SËJ*~E²ó1ªÌºÐÙ qɎð¡\ØК²¥ +RQ¬³æ$±I¯Ö•-G;0fHé0T«®•?Hò°v‰1ˆÔÜlLƒlÑ'sŠÆ6Wõ„²¶Y¹ôG€øóðª endstream endobj 19 0 obj 19934 endobj 20 0 obj << /Filter [ /FlateDecode ] /Length 21 0 R >> stream +H‰´WˎX· ý‚ù‡»)à͍DŠ"Õ®lŠnŠ&‹î +cê6†ÇN`8æï{øÐÇv³é{¯$>Îá!Ã¨µÓHèx~óµ.^ç“ÝL§Ì9Ž4öÕ×Of[eœCÜ&g7“ãÖ¿ídbr³â$#wãä¾}—ª„qžl3<çIŠ›âi' K«qïî*Wi¾i5á#ýL$ŸÏ]¾ž«¯ÃúZ§Úìå:ˆ-Dy½ßÓhºÑÿê?Ìm,KóD¬å»¦Çߗœ¦c/7ÓÆT|ÿì¦ßÞÀ™™vvVÿ5ûšøՐ‚ãuÝ1Z[þöq +rr¼‰ÔéÉ+³¤gǁnCn%3/'7Öã>ª$§ôUB˜#ê1èl}V@H—¤ë@ņ§¹/œÊ¢ÇÝM¥”–‡dŠD Ú1ùŸq„öãCyöDE¦©ïì/¢êñk?Ü|wó‡~óôÝûo_ݾõÓÛïî?Áôäk|fhûq(Woè’…ÖÙ£¸ª'º9:“Äsç6CiÆLWbt®”uH„©ì•J!’Ǩâ“gy|ÿ㋟_>»{ùö_{ñþÇ¿|[.ýŸ.?ÜÿüòWÝ`d†úÛh"A Þ Ü¯nXjN¤-š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_ô\ZÁò°žµEi÷Øë#æ†#qít3òÙ)f‰di ½56ûL‹YÂ{Üâ{½f=õª«c7Ërð±¿JAóÈfÁ»Å„ƒm ppÜO ©|3wŠÂØpù&@«NìLµ+žÒ4ÉA³×ÈKº$‘Õ0œ64Jb-§ós×ðusNtæn ÙÖP×Å·(zqsoæ›BYô+Ì d¢^÷¢Þ–ù¹bF%2:+)q[çBLzÁÍHmEØf43J1H¬ŒÃ ØL'ÙÊnò2Ïu¶ŽêM ¦“³oð, ïR‡|t¨¸Êٟãt+0¹­°@ y~³­²­Ù_iûØx¿]5… ÂkC´ Þ`TÛ£‰¹¾WѪ0ÌZל +õ½æ³ ¶r Âq­: íáû˜ŸaÔ6õ,3C½Pƒ$j`ôø:`§LK­—ïL0!«ÒhWηÒ%,w\ê.ý2×@3ûj¡½jM«¾g&]«¥°ƒŽ²`ôÏPË3QS«‰Â'?ï×çàEJ:%H\I.ðIZalâFX©_m‰E˜‘I®¶”•óBÄrÖ-mb­^ˆ.2-XˆÕ‘snÍ®5†hJ„Š’Î’?¯·ØŸÃj Ñ*! +ÞÒ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¥|§ý#KûÛ±êç´Diÿ}e\‚®LªÌ«iø1öL™wHûdEp iâ:D,¢b#²¦sê¦Úõ.r”9I5¶_Ëð³ðh°ŽNél1%,¥Ûò£7CR§%ۘ-‰­xëÒÁµCjÍ튉´øÓò—¾½½–ûÃò6Ôó‰…å7TuoÆüõ‡GîÿúÓýÓ¯~~ùËþþï¿>ûùÞÀ¯žýû¿÷;ø‹Ç÷÷?¼xöô¤èÉበáÏ÷¯Ðõ/qã£ÿ²xú ~üèÕ)ŸþvúÇ?—ÓSùàÚœ* ÈÙ××{˜@ï`´Ór-r€D< ‡‘»ë¶ïnô (Pq‘–¤Œ¯1³:æz“›tgX€ +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>ݪ&âEAb{(ƒ¯üã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§œìÝïO977oŠ¶çÆWœåìùµƛøÓRe¸$O•|dGa“K50ͤ0¤Âó{x=`´úýƒ’µh«š½PDkÞúùC'¥jþ‹QÒFìÅ¥YÅú÷Hõ’ÒÁý~6ïѨÔ4ü}ÑH›ù¦Ÿw)ژìc±‘0BZŒ ŒBUZæ÷qt­”†é”Æ!z}퍒½¾ŸQRÞ|£x EÙiÈb-bkéăŽîƒiF.ÉïâÞiÏ){5Y*kLÍpw»á+¬æ¢Á”¿»*-¥Mk€KÝýǺDƉѓ+<Ü¥}8‘ÃxÞZÖú1¨e-˜ 8µj¨V¥e%ẀPµ7u—^ôυÇãõq8kÕOšvH ¼…õ#»¨(¥È܄JIŸß¥hí¥Ž­Ù›ØµÔ|ttòΠԹ蜓¸áa©Ú"5E3rPîsÈtŠnˆñþ¤¤KmÛ×><ú̒•.>×ïJWy&UÆÊü\uîV òƒÇºø<ÏÁ+4ìóÙ9`xiÌã÷“¥Æ÷Â'B¨´öS´$ûd0TæµÍŠP+ÛٚRwæê‰ÈÀ‚*wAh,#´7'>O=@îƒìK§‚ +æÌ]§,ËHA½.g7jK¨Û¸ÇE\ŒØ"ãȤK1ºñ€ÐsôÅÁ!y @n:zV<št>æ²a/LäBÄ×¾4ŸÝáìLexb1™ÓþξrÖyâÁ‡#z íuñ‚©>®YCÄAn5‚åÕêÇë¥Íê –ü¨½´EcÔ _Q܃¬3—Æɏz°ûÌêŠ Æz'Ñ %¤gÎÁQ@¸C2¡”:Ҽ݋DµPŸ"¼ÏÒ¬àõù8©S6 ør‘x.DëJvÄR¥õñ®4S%«iª@´¹˜ø»Tß·*àE¨—í‹ÿ<½©.ëõ_„=ït½dÐfb‹wdß. KJØ+ÃGÈ”Ìd Œ7ž…埼=*u‘Œq6÷á¦:{3`žä˜B+l°>'-™Íë~4àÁÏà{¶W·‡Oyú´y€m5Oœ\a«º¬¨ËrVDcòˆ•T\ª­}%ìNÉxÊs´ Öbí²¾*óæW^5úÀšÈ!W«Üjv&w±XFÝGàð€+G¯}àõܪËbuÐ%ìàjbWw(#hba­Ü6Ã!¹\Î4æ^9ä`¤u™ï_®V`Àw7WOÛÐø?ûÕ¶Û¶±E¿Àÿ —Ǽ‹jžì¸1rÛAåŒ15²¦‘ +/¾ä뻇œISc¹©<>‚ÊædfíÛÚk ëªf™h‹…Öcy¯«7¯Â‰ê i‹4XZ ŽÕ?WÝ«nótÊN +Öä×Z ‹;Ucôy¡aœ 疯{?}Š¯~IgÇyU,¾ ²Äyڏñ I[æý¯iŠ–x6ª­#nMÞ#Á¿ßíUð‡‘”Ffÿ€énŒÎFÿýŸ3š±³¿î =(v‘¥´¸¬ðAø°dA­4Í7hÿþsïÆÏ{iCï"†”&Ä×Õ¥(SÖK®AjpóāñAy »~4æ|é‚Ĥ\ø–‰v »7‡"£êµ¨Ññ´÷>…¬4­0 Õ,lÏ\Šjk1›‘.üªoˆØ‚È}Ф9 ¡ç\~‰:Ý¿›? +SI]ãú!O¬rc©ÂÃæ<; 1`¤Ó¬¾|´w ¿R!ÎGï¢qÄ]ª1øÜ%¦­ã暰‘pµÙW™‰´ öïæÂ‘L™ÃBP?êÂÊ4‹8J—`‰ƒ“.1A|EqÊ*ƒÜó„É 19ë5‡=íŽÞ{BSŒeLœ<¾ì‹ ïÄ +!_êñ›»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“‰¬pŒE¼À<2’T§Õ%-‡õ¬½×N¨ æV8Þ³K·&ž#­ŸÖצ3¼æú®¤L`0«Š "áAËÜBKŸ¸h†Ìxuž¥_r’–$½98hÌõþ©Ø;_±/®Ó|šþvú‘P¸fïPþý¼wøûÙçól†áçþ¼6¾NK¸ãf´¿¤)|;`ÿc«ì›Ñ۽ǿÞ"Z՟ÝÑ᧴l,Võ·Ã£Å;|xÛ^\£Ìñ· +‚ý`r†Ix;œéÀnû”Vˋ¤D·ìôz‡<;¼Ñ·])JR&‹KB;¤Ù´>l‡K-ào×}Ûa¯Ùr•¤´’¼HjJÃ4«òŸæhµ ‰¹ i·¬H:TùW ¶?[á•™i´§kË.(¼†Vظ ý“!¸ïæÐ,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²Ýô½žûnDïكþ»±ívƒë +`Ÿ‹jè˜S«((6ùA9{$ÍòŸï¤ú7[×t5üŽOU>G ž&ÈìZh‰-Èwø ûå~•¥85Vk 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ñÞÄÏùɎÞwéöáõ`èb¿#³raœ-ø9ܶ LnFñmoGxÏL}º:Æ·˜Nh–Ý]Y2Ãîµ|rx‚ç£÷{‡ÓßN?Zâ¼;_kãö‡z–¯ÍnLíû"S%G3Rƚ~7¤ô¶½Kr¸Ý]ÒäÀ4«òÑÕÙáH3[™ÀEEìb |¶í‚Z,×;1#•C"k˞(̛Ҥ%©0òbOåîéå ɖây& ]ÛGCÖ¤bcº˜Ï l*´¡ s÷Úóçv¯%’þñö}BÉ[‚¿©yûÌ4S÷;°3¤õëe•_W§Éc¾hÏ67¬ý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 ­íЉ@‹:°µ+2‰!®Ü9…>} ?*!cŠÈEµ à»Ï÷Ý}¦ê“)bvüÇm`àÏø‚,qãÉ?†jÜ» ÕO«‹ŽO‚€òåÔUB꘴1ˆ¤a'iÍ i½bÐJÃÞK>—pí r¼Ø`5ŠŠŽàBöÉò¦9bLœrÄø<†¾‡ ³:7êšø3¢N øÓa± ¹kåíkóVÏúˆÁsËÊÈ©z$ +CF‡GºpAhQdvn›·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Œý·»*ÅDk÷µ•‡AÝcHÀ”5¨¢NkPv‚²UªP¹¼Â ‘³?=ŕ°ó“µ'„²ödí)eO Ú,ª=õH†Œ®±¸±Båu錑øW&šRHBÎ¥5vwuRÛ!ùwȆÐ:¤,ì7h- endstream endobj 21 0 obj 9050 endobj 22 0 obj << /Filter [ /FlateDecode ] /Length 23 0 R >> stream +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!]‡šÅêªJýRßуé+‚L«¯>5tÈ®8ÉÞq|ÀáKÞ©­OUœ¨´z:!ïiD…´g3·ðÕV=‹£©åÑvv¤IñK®ÚQ ÓĨ-7„;½¯©±ñµÅC4xÊ=;•…Ú»¿‰æ&Š™Á2ªŠ}^äùç”pdBKÒ«êôîŠôcH:’^΄–¤ƒmŒEúю;íœéiûlÍ·fÿ}þ›ÉÅs8¥Ü÷;¶ü„ÝbëÉçËo|ÆÄ7Aæ—XÎr%À†\¦JnRHM~³À ”•Ô=4P3LÁ:¸ï DëuZ=$Û¥ ycÕ·‡,lõÚã_£³ü-åQ”ÿ²æùG$ ÎáÎC@e Y¬®ªÔ×_¢\Ϲ„특Ôiuúˆ +û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Â41pW`Íwz_Ss/*i¤IñÇEìÛނ›o„W®ÊçÒ۔‡¡M«Êéùô5ÑÜD13XʪØßEžÞA$H¬U&´$½ ÚB$Ò»+ҏŸË©¯H[‹ô£×µËp-€û®1$­»÷C綛]†h4í±Ë°Hßc—!éå}vé»î²s¦§uì±5ߚý÷yoP$—¾X¡/ý¾ß±å'xÇH¡c<ù|ùÏ˜ø&Èü²ªÃê>KZîý ÃôOÉM +©É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®k®G„ªëË.’ƒ|ù¡®ÉAMjrcy¤ÉAÏ´°ú±}sÐGò÷ÉAX¤79¨ÉAMB}OƒÀñõ¶Ûû ÇÎʳÜx´¯ìkNMéï¾ë Ñ#×gˆŸF éc¸ÕëSp +œ|¾G„ªëË# ™äÒÀæÂ0ýSr“‚+jò›f ¬¤î¡ªš53yçd{ÈÂVÿ =þ5:ËßT6ù/kw.?¬žXÁdP·i"’ÇÄ \Ôî*TšÅêªJ}ýE(Ê­µ|è"W $G’²3Íþo]T­§š™JúKsC«bêüÝÃA²ëËÿ€C~&µ-4¦{Uó¼îø˜IõÕDç +L‡‚KFª@kƸÓ\jò °±ð=”J"VEÍ%J¹á•Cµ0zA*÷UÝ8KÑ>Œf©!´™D\“9’fYJ.“$Œ˜!Üô\Rrg…\MAøC$Xo~çb7G¨I`ˆ¸POSRí#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ʁ»K7¾ÏÍHp0<ˆÓí¼,«ûÍËwº¯Ç-âiøÆ/¾x¿Xå˜î&ö_8Ž`V¾Cx× Q¾²]®95UCv©¾ÛÁ¢ÿF®Ï1>@ï‚âVï›ÝÓҝ¾<ÆÓd¦&3=Bf +5qR\(ž6© Wjz‡ƒåBš&45¡© MUMhjBSššÐtïÐÔÃӄ&„¡iDlšr"ÂÖñå`<›{¤IñGU¬:í2Ó¤úPd&xðà!X6©¡CvÅIöþg|É;ʼn¯a˜2¨â¶4¢*Ú³“U~úÙPՌ"JûöµÚTMOÞÙtÕHú™®¸j +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~瀅¼ýcŸ±=¿™Ós'˜¤\ÞaÿSM¨%â›â¦îڎǸ¾c££8UNS· šcuôHA€õœö Ùo·ú7fézø Ã˖êВtùd…gu„5@oSqèÅXni6ã"nFªiy1†¥@x£*½WBéG²¸è¥¯»8ߧ¾[myµ]ÂãÅó %ÄFõ.EėÕÕpõSõÛú?F_-5Jþ˜8c8‘þ{1úg/xŸdÕÑ16ùž9ñ÷ˆyºÅ0JnIš›h4ðNTÛl,§€ç§:!I”ê PŸì|#ÀP{¥ )7/”á6ƒ i㪶QöSðAö¸‹á¤:Ä\Žúº•Jé¡U0M¬Š1^ý1ìØ›Þ3ÃY¼x¼GÐ뢑ô&@ûñ¤¹@Ú²%“v¦‰4)ë")¡‚'éÄ|.ï°ÿX2Géàõ7-xy?½s2º¾§ ÆM©5 endstream endobj 23 0 obj 3623 endobj 24 0 obj << /Filter [ /FlateDecode ] /Length 25 0 R >> stream +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±>ÙÌ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²{pP¶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£ìµ“±¢êFíŠèmƒCv‘,ǔú†‘kÆÒmšx?"œ¨—µÂ<'…‡ã©3š+ÃGS¨™Ï¸ ¹ÎĀ”'¤é2óð0Œ!¤¾-â뙙q›ñ«GX¶$ˆƒ1»D]6‘&¹Q'Í +†—À™¾\1Ñd2#ª>b©*Jù@cÔÍ §Z‚ԝ‹R67vh¢³%ï™óž hËNyà4$_uz%7SH?q~72‰¸8‘n(”Œ‡úe0D«QRà›ŒÇÀþP:‹Q“ìç¢vÁ.6I +87E/†±€ŒY£!¯Àe\n(èª[šrs¢žÓ¦:È,Üìö +ê’éF!®ˆ'©‘™Ù4?(Ɋ›µÐDÂ&˜àk€ª×¾Ñu²&…\ßj*&cømd«³<§4e(£¨ÍD¶ZéÄÒ¥…’RU£A=@ø©ÚÐœ0Ý=²IË+ÀQÉš5Kàf8ëÙïM€at2–°Lpl*¨T»vÔKYV¡ƒµ0TTÀ)T[š9Ÿj4õd©WÍ\ɒjéw5¥o…Èú9s1ˆdÔ65“b …&)^f†XµÁŠØCÓú€¥†´>u³ôŽÂDZCOM®‹ê ù<2U­¾WυhK+%ê)¤i±í…Ü·Û:ß ÀV+]eûôÕg ¿:êÔ +^¶‘¾‘ÜÖÁåÉ ë‹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íèL”bá—UÐðŽ‘¹BtƑÎ5V©jŒúÞèÓnP¿äbLqæh}EúÎW#óȨÎW“$mê EKE¨wÐd«L GFµ„ÁPO¥œ“Ý´‹XÒK蝑„¿y– +¹NBá)Ú\×YPxA¾E²b’Œ Ï€zošš“Ó.,®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ù?݋민žÚFÓ0¡¼Ã¢¯©‘áJA#åΰ}(gÄT˜xòÈT¡)º,³ò¾ã‰§T?e¶dOG±+Œ+RìˆÔ$­ 2‹V &ŽŒwùÝt”A>bížF Uݏñ?r?ŸÛ|*_”[7œôDúfÌ\"zâ óe“µ6‹‡ŒYêú¸Ä„¨ÝÚXFDùm×ì‘”Ä)‰°¿"mƒ™\Òµ„é¤ÝÞ<™Íµþ´¼²ŸŸFÂVÜÖÈÞnútC±%µA±S¶ÑFž{º?õ÷/´cO[ ÎÀAì¯Äªï.§¼¦8}ӊ9¹†Ž'¹0MzkÃ]N‰.çßYÛ®tQŒÂXJGƒ°­.ÀßD ܺŠ‡Xu¼kUgtAŽÿ/óÕÚ£åqCÿáù‚RµÏ}ÚO»I?¤M”*¤*RU!X–KËZ"þ}Çö\ÞÒ*‘ª&ⲇy<öñØ>öAI‘–—ú,÷!8J§šÇÊó’A-¢Ÿò *ZG½û÷F¶ ®¹~ UWÄ褸'Ë%‰ØéhÈ^s× 1šC'ûúxįd¼Qšó•›¶t$GQÏÆDööŠ¾ÉüÂ`1cÉë"KÍF^Éó”µWÌÈhM½*Ø×jzì#…ºV —˔Nôæš Ü«‡ËvÛ*°ÊaÓ±Š#)0‡ÍVÌ®åltM÷!ìnQ…zÕýŽ{l¾_›]ÕT¬Ze@äeöQ8>ëûø×{íxððxü·{ïçŽË;ë…µ¦«2Í0׊®AmT/ˆ—f˜á;i“ ¢ÃBÍƞó*¡3o9QåÞ° z^/ýr‘ˆ²NxÏH®aíÚ<{¨T½KkFû†£º<’†Û;©80=×C©uQÎ_S4Arü×2ús6ñ€ÚMÇ.‹äµ'‘ÎI\äý"‹¹JjpY¤'ñpuøâ>H$]x‰,+4úŒ^¦`ÔæÑ0»ôp‰U¿È]}1IÕvLقuµhÿï Åh1_çýˆ + .§QëçŒ&¦ÊËC”DîðZ€  ÐI ¬¬©ìåî.0ʼn‡†ÓÄC–eG’ÏýQ/ËE“¤ÛP®&c»ÖW Žü~¤aŒécÊÒ &Æú8…2òc0P•žÝI—uŠ;2ÁXœ” R] rÚ3ÈÉڈ +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±AÈ3I"ž:Ø(jåîs½ï:ã,l¹¤Ú:Hç`5ã K9¨(}¡P˜÷8-òªÞY—Ö= ì Î0sé5À(ôঐ» +CLØ[‹¹»’À¢VGÖ ¼´ÇQTb²MzZÅÍ9\· úÑü°qGfð‡ƒˆû—2ÔGoü@nLN܁”µ‘©Ô\àñ™/è¡Y÷X~íT³Òª³Š¬Ž½f0¨Ùx*µêî’*͎k ¯4ÎÄ%¤p0@Ï*ØDÔrâÊð'B7Jn–dˆ™*ay_ ²•€‚LÃ2ÒµøÌI4ÒÁÂVAÞn>C ó~ÿÉW—w¾y}ÍûíÓ»OÇï=àù˜¢Ë¯}¸{ýöåñàêêòúúãíï><壏ßáàðk×åÒ6ÄEÎÒÄi•%‘%«æ ›Rìgád ñèJëƒ‰ß µ6ÞW—4€Á¾w–èZšž%ü¬ïKÊ(° Ô¯ñ0¾•¹&I˜s—¼‹d‡.܂v{%UJ}ÈyRG½Ô6çUVYDäª PØ¢©\ŸŠ¾½‘b¼R-)Fé³Ó#Ô4î÷òö¸f8wB‰÷Wb²òL”Ó¥<ù)6õE#U& NøKÏ$x  ò_=“ÙVÿÓßî_~KîÉß>ôéöÙ»7úóÕÍË×oypõñÙ³77¹?ã×ãŸÑ?øÿûOøW×÷Ÿ¸ãþý"öÍØüùñ§ü ý'žïÏG<¾?þþw<ïøãñB:ÿÑ®±„áAï8ÒçøqIã ϯÇseªëÏ×òmCÊv’TÊÓì-˜ÝÝ¿_ð¤Òlµ‰Y‰ÖYÆÝö³Þ=|\΍X{gqó÷_z WËxôêéû›«77oŸÿåé‡Wß~#oàÁŸ¿xä§OïoôÐåwß}öØ÷O?\¿úéÝÕ͋ww7Ë£êݲë·ôšRŠº–`kÊ#ã°PF(`/;/†Iú&Tsóm•ÊŠ¶•rÛOW,’º;³3ÌÂünBΛٌ×Z(ìNhù./§— §Ýs&~)¥—ÿ›”^¾øps·e à‰5Õ_£ÈUÞÿpo-듢þ|I÷‚þ|9ÿúbþm¥üë ù·•ñÿ[K’Eö[Nc…¬i•X°ªê.ýFòß©jтŸ>=€T&ë.÷³ß·[;‰´@„Pˆ«Å´Û%…ë…j=6£Hk2›çýŽLvO—£3¤Åèyø}•»ˆÉǝ¸É¤‡‘ƒ|‚j²Bÿ¸RHYIÈh‰¼ú[Ì (Ä!ðҕÚ8 6°†èvbVWPïâŠփc3Ú·»Vv 4âÔÙåìˆj1z¾ÒæÑ7Ú2Û m}•éBçäAœÂ¼_dÕ­p¦ïUžuˆê7Dø +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‰ñ7çû«©î¢•šPbg”N΂jN«4Œ<…f YÈV3 Vj»òÄÛåìŒk±z΁°çk*{w˜½ˆâђÉMÒŠ(ûào¹N(ºNŒI=/Øq°{^Q¦¡¤ýlÃþãÊ‰Õ šÂ݀k ¥iXN…æôÀ@ãN½]Ύ°£g(sL÷ÆÜ©â¾6˜3Ø]8×lJ¤$|z4T•£,}Y@£.ÔZö³-÷F»ÕT„º#J“ãÓ*,®í(6ˆ_çÉÔbòœeîtÀò=a.»åCx‘scÒ9ÓÃНŒ€i^Ìoïe¿SPˆÃÒd‰œgw¢Íè‚éýÂۀdTچMŽ6„ýzÅmâé<9#šσWÖ(î#b¶ƒ¶Ú\o9РÍ`t‘lFä躬ˆÌ¿‰òÉ}³5Ðxó]|-GÁ…“96mNL¯7ÖM¬íã±DWÒ}j^­˜q¦^Γ3œiðõ³‹ÕTlëUazÕmÃ(èH•hwÀÀA8»œaM£ç (w9ŸL†©QwQe(Úµoapg0zI6Š¡‚*‚¦P×1´âw‹a?‹õ [Ï¿¯¶Ý:Ž#øü‡ó"@zî—䉤bĉ–C>b,¢(H´ ý½«§«gwÏY¡ €*ÍÎL×tWWÇí®+7°ù0Ât-6›‚à²ß^À@㎗]­]ÂZ6=g€Ü¥pÒP½§ÜtÞ|tšû8hkçuJ½R|y²‚ ®¯0¥ҞêvåžtH]ö\<ßtrÅð$¡­7…ío¡ùÍñ†Mâô¦«¥KL«=Ïã's§Muqw$®ÀU‘ñ֓µ†‰b2QÅ]Ú|¢È„º Úô h¬¡c–íZ˜X˜ö²Ùs…ñt3фa‹ÎXã¦à²ã·0ÐxÓ»®–ΐV{ž¯¤£°a-ôZNY‹46X^«Ÿ´N(™ì¬5´Ì‘ÂÕn÷®N¥ —¯rҎº¬Þ©/›. ]À˜3¸à„¤ÌÍ]A‡«%lo`àd.—íÒU°Ë¦ç »:+“Ü-Žxr—[IúeȸØýF˜3ŒqéVBZp‘W4, ‘ç6¨šÿ8aµçÄìx>ˆâµqºcA:¶‡8yӋ®Ö®"]6=ŸÄ¥™c$NºÈ q­ K‰/SB«¾ßÂ)a‰ÖzÒV%-!óî¾×¶•¸$ER¶k›pêÛ]Ðn`Úà2ëmÙŒÄÃö{¼íjí*ܲ®âÈ^˜d‘½eœ {hXÙóî%ØÐ:Q„ÙC¶ÑudL¢˜QDÅ7 ÆŽÄréd- YºvÙuy3ƒ†¢¢‚VܲiL5n/`àä®å“µ«`—]Ïêüáúý…ÛR—O‰ϔšæÚÂᄮí•#ˆF¨´yŽ÷.=ôhÄÅ\úvm“[Ølº`v¾¹ƒ z§3â¸)ȀŸÛ ¨ÄÍˮ֮‚]v=g@˜{òãן½9>¾yx÷òçß=½¾<|ñüñÛw?ž>ýòýÝÍÛ»w¯¾}ùøú«g—‡?Ȓ¿vÉ÷ŸÞßqÑõ×_ï.ûæåãñõ÷×ÿy¼û KÿŒ¿?àådzñúlãõ^]”ÃÓËË^<½~õðÓÝAŽ8Œ]>^Ž•^|{ñ¥LÉéO®_ê³Ûñcpr7Þ]û’¢›?OÜçð´ûÿñ%οNaûï›/Ûî—v¶O§Á:Íèü¯¬–=¯0f7…äÔ: ã€S/|êè“zeHv©”ìXB4P»êH +o}Z¥å˜$œ3 Öº‘j9ª„Ãs—12ÄVçÎE*>¨QÁô‚Áo¸Ø;5Ðâʽ +,.€7;ÜrÐÁ$TUK1°é*•hW9&õbžF+îµ°”‚ª`«,Ä^¾¥Kb;ìrøùºø#vu^m•Õ77×Çã/÷ß=<¾”µ«tÖ§¸ìZãó`ÎÓ.ߍÇ^ãÀd ³,ê‚ÇBŸc\^GCƶ­h¬ÅT4ÞbmöF^¡ªsHø>-ßGàj(Š#7°Ù1Rísç1Àâ†!%«5“\DÕF*%\%”Ƶ¹{¦’ë¡vâÿ<µ8>·VÜÿÅìu²þíÉõWÞýø—w¯žºÿéá-ÿ}s÷ó›wDžÞ~xøøñðW‘ŸK©1ü}ñÛÅ/ø³Ôšµæ—Bs‡¿á—ÿúíßþõowx%_~7$\²Æ›{o•¯8ÏùÈ«gƒëI‹¢I»Kl 6Ab¢Ê=L7ÒýØØj¡çk¾G͑bE…d +Úº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žoQ8í(ІñÈftjuY•Äyg3iC€{êfNQ‘NNê– Ub©´¨žÅ°ç¨`®Ñ6¨ÅqåÈ]ó¨];‚$ºOÅBpjÕ`a}ìLÓÚ*ã=ɼ^Åišþ•M±áYó°e ¦:}HpèøŽ£6Õ©¡¢»™2M];×f͙ó'8î¿ Ÿ Îp¤€þÂá“9ôX½™Ÿý×E_§ cŤ·µÎÑ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®Š{T¬ä4k9Ç´lá*:k¹g­eaͽð–:_¼ë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”«èZq#ÇÃ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¹ +*}¥ûéýTï¦d&K,°šEîðè-îÚáëôÊ¢ µ²„·”—FC&Ñ]h£¶Øs +ŽûÌ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ý \g”¸)R@ÍvÐN¢Ÿ5 .Õu.ÅE“òÔb›à–¼ã>§&‚ÑÔ;‡2„Ü(y­§ê˜6ñ™#eJÖIH²£Ž”‘€¥´íuU­¥9—ä…KÍ/ìžC×-ã«hå/°XHxp‘£\ñjŠFë.¬ü(~ðh¨‹øüÎvÙµzQøì˜!¹Pú³ºû2 + +"(₈è$ˆšÄ ‘ï©:§úÙeIv¦öí¯êêSÏq¾– t›&´{/l*y;-i¨4"ZÒ f-)öÓ¾}>¯ïº ×½«ï74¾ +Œ^Â,¤¬V +=¯r÷ªwÏñU7¼;öðΌë‘c0™ñV†Žíå¦|Å A‰Ô§C¥I‚q]f=r‰¨…~¡`›‰¢Z[z[•ó˜é<>ÉÁÛçS“Špî̇šŸŠF?Ô÷l0òÆ"Ã$h)<†wnìQP–Äé;Ü=MK´’Á½+¸íéÖ«h°W¶YlÊ7ŸséÀ• 4øúÿªá‘ÉAªXÖÉg3ðöùÄxÆþðá¼|ñåËÇ?⧟|õ«Zþò‹ï¿þýßýíßßê÷Ÿó÷|¯È¿üö¯ß}óŸ/?”—¯ðßÇ>üÊËo?`OÎ~€ ýe òò/ü£»Qüè>þˆßþùR_~x/¿yùӟËË×>×ï ¸,`Y+Þ-$éÌΆ™î =E¶ ÍY‘±î§Ôc÷¬ÊѪÜQ4tžÐõ"˂«k|.ö©/W\ŽÂcä·Oæ·õ¸Ïã80uG Ç«Cx,îkÛÑ·c±öÎ¥Dö¬ŸþMãû<"¦dç„‚²¶sü\ù¬€ìn@©Bx/6D˜.¼;Uz{+Xw¹ï²åkƒ°Î¥ê $bp¬¦®ÉâCpõ)-''8µˆŽv#!HÃ]Ïi¦5±o¶D˜Q£Âc7Áv2`dÐjíÄáåû•ç‘Ûpôki™L¾osî v–?ã$1®L‹ïª·4œºlˆdß=!y¢^O"·¨¢ð\Ä|_j¼Öçj9Aߪt,¹îs¯4Q[† ÷6·1×á‚·V×b°íù0oÃb â…u}‹.<콩qÙ9’ ü–=Î}žÛª,çh”>3 ڔíh¼¸>û¼´³ä(¦ŒŽ§•/Ë]ëVǀ³Úaâ†ÏŸ`“…3y…€IìӃ&„PZ ´ <×pÖՊo×ɳ²‹™cqÍØFRl~Òt†"Pì*<Ÿ¶J¦t›ÔêQpÚHóå3÷ß's «ñ${õ؁ûǼTÚ¿l©c’U6Æi ìíDÚÔG¢}ç¶|¯®h9K'½m‘ o裙J“£O‹£º Þ ´Ô`ÃUÌÑïRó0Jžš;”ÅP¶õ¼7Š~,[î°ª¹úv ÌÐâXТ±U€ +aúði¾V|[ + +Si—éOïÆuêL¨wK»ã`SïèäÌY¹ý`þ .ÖÏÞcßñgêVo§…YWý¡¼{ödš+ØJæ:IàžÕ#h;éžÔ<%Ð ø\‚@‰Eo@´(ÓþiË ¤½ÆS×ãjjt<…+ªöÚTÓÕFMühÁ/^Qþ:sôÙªÉ|*8~–´Ý°\*Þt¸£·tdÒuœyÛuEÛ4uVÿòD» +rÕjA¾Ù}ºÁæhú’8`Á¯—YW/Úºì5ç·H¯1+>ŸµvnªÚ*Ò¾á +‹ÙÛ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„ŸL4te×lQVüžÁ>âKh˜üV jÚwÛ]ãg¦ ³Ýw‰áOÃàZ †@°;AËނp:¿­3ˆ;˜)ÌPÞpˆ#D±sÌkyµRb|H x­glîý6̞áÑîÜnvIø |“ÍϾûמð`EîøJˆr°%¸b5ØWKÿº¶ÜF·@Œ-ÕDp Ì­u։NíX °W°h‚î/v=É+ñT]@`fý[sE;²uU ²|Í ©‹á×"«y‹oÈ!õŔÁ§±§ð¿.*9©h%Çb ^¹@-°>áÇñÄõ-ö´aõm ${U¥¦B³Ç*-‘êèîH:Óm¤qýæFWÁMù´öôÙbKü‚Y[¹ø0r%Þ.a©R‚Ö¶žÕ¯&CyÔÊDÒWòñÚSNô–1 ã[™¹¾%'w»þÅApSb}ØRMLê<‚A÷oº?âŸ`“4ýN)SÌv¦"‚($9P¿WËÃzr5Æ7±ÿžõGüÇV{w}6•SàÓ*Õ¡îƒP…à´°ŠÝk)¤¹-Š®ØÝ3¨ÖÍû0Xw7¥µõP´.¥È+ C‹0¸„¨kì!V§°Ó(4ǧš/ð G!V f¯ˆ Á1‚rU e=yü#ÔGïa0Sj­SS–>³%5Æ‘0{Çìñ€šÕËIÑ"ÿ¶MKµží+˜ö§ºíP˜—í^ϛC,v])”ŽÒüV¯ý:çG×Eð”L*f5"сe­"æ¢ÃOO¥õY ÐqW¬ˆ „›ãqT¦:¤ôxµ£ôÉ睝 áÁÎÖuö]ÝÝÍTþŽø:Â˼ÕG‹ZŸƒKüºÁHºŽIñ‰ð™ÁÍMœ }ۃFP##ZyÄZrFd7ïørêf‘!ƒQyçä¤(Rl¬fâ… eâ=*Ej´[Zk24­äòøÁ‡,ŸËì}VÞ)JÈVÁp _|«V9Œ˜L‡r–ÉÇ/þ¼×iú©‘êÑ¥ðáCíŠQ“6ùªßjTo<ì1ÜQæ0Ý.#A·Šœç+n€™¥þxdµ‘íƒ1ÐH^V•Op~¬Þ,Jª³Ç,=ÛoçoÃÿ«QëêúTüÿxõ«¿ýðûOŸüðîó‡ÿ~|ûéËËo}÷ã¯_~øéó§ÿùòÝO?¿ýåýþýþãßÿüöóÏÄ?ýÆò§ÿû“¿|ùå=ô;üykäÞÍÝã0žó¥¼üëUyq“ŽJ–øï¯_ÄFÑðñL͍ãÛ6q×\CqÛÕ]{ Ôð,–éÐ=#½ÅrƒC‹N‹ðFPŠêEÊ9 †¹ÁtªSg•¤Óø°¥‡Á½çcRkT’Üx÷;}Sœ2Ù*I†Ä ÃvÝ …•ôÍTšlËÎrý B>©žY“kŠø‹þ‘nªu$¶\o6W¶@ËoApUVÌM‚¬$ód«ß dO[7žírÒkÎTŸJK§\òF%/»WM™O¶²W•÷>ÑW>}Ï{p™qƒ2G^ÖöY]c¦£Öy7¨)"A#ë3í„¡NýQ{›—jÓ¾<©:ž˜]eÖGÀÙ(קÏ)üÁÇ£ö}©¶p E!OoF¶'Úδþ½®u›Òî¶ÎƒêW–¥N±^¦­_w,ð<òïËÛ27’{²õ|¿ÃPa³“”iÂzo÷ÂzÎy7…?Ï:udÄÖÝÍ)hÿÖK,ýXÉ!–N Ê +÷×V°8 >6h¦¸v/`ª€G‚›¾Ñz¤!Á Vßa!À¬E»±QEj]YúÀapãY€põŠ{³×)P®:z=i0þ.q) ì3 +:@³, Ó¸ë ×£ÈÒA4ùä´´ªvï•Ä²× ÆiŽ¼äͪ¢a€éAœOs½ò ¼•üüz-lÐèÈ_ÕF£Èf©D®”ªÀÔsy2çv5¿ƒí'—»¾Äœá=g§þn¨b¾q¯Ôôíq#X–©uöH÷ƒ9Y§óá!Š&îBžJltaV÷Ñ»9Ïæìõµóó>@-»ç÷÷Ùé?ú|œúšFÓV>²tÕÉ(Ø ¬8“S«ý^¢'§YŠ* ø¸[1éŸá…êÌÒãbŠ„È +òÊÛ€Öwµ> #EÜ>#ùùL8UWõ¡õöM|ÀWZ×iýñü³ +µ¥J—l™ÜÍaÄ;µåópݼµï/§Uù¶8A\E}~xŸC_-?%§¬ïL¨€ûšÉBŠf"#üÔ%ÙA×zÅí›<wµ8xÓ!`¤.¿lb³í’ªî_+YZ¿n™7άy/:Ÿ¨ð´L$Sà.}±ZO7˜#Õ7ص¦Å¬MÜ Ö)%ž"?€ „.Ú×xã­ÓO–›|¦9<.hE/°ŸÂãie=`×cSà{w¹y€£g¢ ã÷F/Ã2ù-0N^¶{º¢I;dt/,Õ'd6{€ü ߴğ¾ìn÷-[PDÙ1(«Ž{U…Â÷õ¬_ÕA7EªëØÚ4²a®Gæì<@ˆº@'Ã–qW¨ó$Q™÷DöOÎ1À.?ەI×Ì<á~ÔϚئ@—&½…†®C»®Fþãî{úSp9øe9ŽÄGÅZ:k£:¨=±b§À˜ŸPË>nÒõ])6îR}öNFJ€á9rƒ°(ñÜ»S‚ãXaà–]òV¸KœµÂI´ýØÀ‰&àÒ{Ž\³@b¬\°+ìnkrëæs—kVV§rh[ÌÚ!È&°ù©¹A“Ïšù ØÖ0²ù `æ$¡1ì®?T ¿íiök,½7{€ÆæMä(I˛ŠŸßÚÌ/8µÑ—±Ò3E½sì°©´ÆüäwEncg´*Á[ÎfA§ßõò¦O[ÓDDÖêy¨’f÷̧÷«Gvùéø;£Gݖ˜Rh̜š¸6£sZ›jRøF¼ýÞëI ‡”X´ *¸‹( Àð^ëÐx'—ÒZ$d€·<¨[¯±t€¹›éT[ƒQJq2Ð3\ç1©J¥Û+ûÇCë9AÑ >µ— š3GhDÑø[€6Nª+ݜÌ\T¢v7ð‰6™~ÈX³_ï”[¦ÀºWºð²DcÓž˰ÌÂfOŽ»oúXÀœ½õTsÝèûQˏ OD6a6ħ†,¼oÀùõóOñÏH_0Œ{ O^“ÒiÙjÒZ»Ù*6oÒ {>6Š7x¾Z-;=T :Øô€cñZ[θåS:«‡^;›*Z¥ªl,U9QŠAY)¡Î€$›ŠNnì߅ ­aÄ">.ÉB„¯GëõJ€8p7I&ÄÚ"¶MdÐ-Õ±+âe‡\V¿÷HË…­îÒÛÑÇVgœ'(¿RÝ §.1çu04ÐZ–ïLP!Ì9«]4Ž¾µ(v„ 0Vž´U|ëO¹¯/rrГ]0šŸÔý¢ƒ•-À±Mw?-¥Æ?ÆA=#¥ÊßÙïöç Õõ$ÿ¿ ¶+ûäàöçäèñڇWõzÖûuðè‰xå¥óžü~¬¤úWD?S€áP¹«ÛÒÿq]-7ÂÞ8¬‚ô²ðûqþ›È=@.‹-`»%’þÁf†°ýɲDRèÀQ'“%Õb6Ðþ•4ÝÂ) »' 9d´OQ½c:wJ‰)ç3ßàE{½z#À¢ˆü­¼,°„Qõ©ö˓¼³VЪ¥…Ò%(³0gJZ !…†¾rÐHøìVß rYpºã§r¾6Ôÿ™Á+úEOd«2ÛD9*õd9íìï ë5W:€àÍ#®…”W ¥@7~oPº¨ïâxjŒÏn>bµÙÃBè³ö·'ËU†?Á½%€-‹0·¯k[;à ;*ؘ8–VJUr †ÃâÎD5¾ðϸRõfvø4@avëY~Aºš!þú¥æY¾¥x¿cŠn5áÑÄ +;ø#ÿº~ô-õ fZÙò´­ªè­ÀYëÝÿh´0ô²ÕÅ;øˆÓm|UôîHW„¬¦/gË\‹Ù"¹5Ø"8OÓÀÞ¶ësM@mŸ¡AvoLcò +3fÇTÑ¿C^&Ä&º.݃$–ßÿû[‰à‰Kþüù«j›ò;ÿ܂Ôî%Ô̆$Ÿb°Ó„]gÎÁ’c +¨+àíŸ5LÉäÊÝÖb¤7}I´a$ç$§yÊ*(°†¡®0 jÿ¿,­]á³ÄäM7ÍÞ:ôýô$ÞÃzÖöÒ9_¨m4cÖ7œíþö7]¿Z6@ÑGJ(Á…›åWžŽ k8²ZMt|©.e_¾8>Ë@ðÔóE_iÌ|Š m“䆳_·K×ɇš…òÄ Ü.¿à£ËØlÝÚºGŽ¡7ʄãKpçt…µñ<ÞV? ø¶+¦½ì#ñyŒ%ƙ©P÷P¥’×x§’Äô®ßéý2«{ +~ZRv`™UàéWÏ<ê÷ùËávĘHNjCÓ8%=ví[u›ø w‘à–‘8Ë?LÔÝç5€-Äñ—8=ÅBÅá«ÒFÆÔZwºì\ïß‚ÄÎÏv ÕÑb¦p +ÊQTkv×/^pMÕz:%tíC’GPp”÷ëZÁQ×o]†ÚïÑZ}í»9yy{iÕ/0¦ H¤É^âÐ÷ydº¿ö…ûÕ·÷ki +=ÁV|è|0—)À\ÌG{:2ñ*£ûÔ«Oa΋HÁPöh %g÷Õ}½ Àh1{ÙÛº`P8“ ’¶æÖ³§°Ro0੄Ê{ATKpÁé ŒZÆÔCøRð¾ï&á^º×Æ]lµÜR1;ˤR +æsÁ¸Ÿ¶´ÖâxBž‡ÀUÞ4 EaJa€çèZÏ}4ª¹ÑŒÇíõÔwÀtËÏt<žqƒ°C¯xÀy fn³ã¢5—9›­°Êv °!s¸È `í™o-=À¤Ÿ·=f|*ö0‚Pš¦F¢LÐÆ#¨¬œ§è€ò:)`Eðõ쏵8 +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Á±Ž¾á®/Оü¹Mÿyæ·‚ ÿ;‡ŽwÁxÏyxY¼÷æÃáüK#‚•³t§µ£°逦Š‚O¨Åk+ÍydhD‚>Ð. +ߑÎ#ΫRœ£Ûv†ç‘/UW%Š\‡Ù®ÇþÀÑ·Ú³Õ}Þ]Þ®PÃxQß)ÊΪ£RS¸e=Eó¼Ýåˆn£±;Ohñùœ½ý~Q‰ábŽu#R‡@ôÑ ¸j1k¢¹¶xíì˜ØÍéôJ˜—ãJÃàt‡`·ÝSF£½8{0…?¶K‘2,8Àrž׳ÛN×Q“T÷±÷Û¿¶Ö ¦0'Xž{ÜX™Ž*°ÉPøñÀ:tÒ£«•ÿG¬àHØ$ߕVméƶ«–½¿ý®Lèt#Ó#‰7@T8Ö_ gó ´P×FŒ3J@˜>6äb)ƒÃ ·êç‘Ð0ßGiNïÇG•€Ù(€ ºӕ¿ý!‹ã-Å@ØôpÇeõÑ,}|ûӉ°.n}`6S±Q•½Ñ²îD;ˆ.ÿ"h¸Õ¸^3ÛdÛú€¹‹ì{Ñ°§ê±À†»n ÿÙL<)®)¸v´VOa!-¬¡"›&¦pfàÅCoÀ‹Š³‹›8ÀKK`=âëÛ«›(dTÇb ­ + ©‡Á²¥;*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&íàÛªĝQ@»û +óºý9©8•ö¨Cñfs&q$XJ{û; nfë¾­†€]ƒÀKËÙ.ÉÊ9äÖâ‘_ºoIÇÐÂ=‘C¿Ân2Oæж’1×µÅv:"0/…¥¹-îúD¨C{yê"·Ppn5˜.½gR–˸&¾†×šOòOÖk‹‘Óæ® ÙñNvqÙË O5[øر´6åúi^)°ýÍ gÖ³[N”]½ ´n͆_öðâe*{í<Ò°\ÉáփsDPÉJtÎñ*V?ß Sj8).Àiӌ7)“u—™¡E*ºì‚9Oœ~ @½M€‹Þ½±mè€=ÕÄ÷:כûã”]¬íºÂÇ„ 0ÒÍnF%ÚîÔ©ç„ J°üÃuµm¶ÚÀn ñ¬ÿ$¼w•7.àìݒº9ݚÅ|Ó@¢­ÀÈD!Œ¹(CK´êª°ÎràzÂ='v‚"¹)>Ë ÆÚÁ|*ž À%s`ìçà×-%hdw³o‰E¼Êìß™N–·M0O’/½áY¼˜W’©*àŒrhÿ%" ;Å yWp×½((¢Æ"«Ž'Í!*u™õW€„»“…ý”‰Hož»z8F–axí~ øJ½ô2¾åÝ ,k“»bæÁ:ý»CVk„9Â^xuiÖm¦8žYi£Z ái”ÜÁäÝJ'p–¶´dtçKÓÑN. Éþž¸àW NØëI#ÔíÐœ5—†>'¿A Nj#dNåëa¿´€uF'‰Å»,Ã*sñ¿Ó2Ôd<Ûi 'YëbÌnWÇ_PlÅ^X­^8Xѭ䊒 ¸¨Ýå°«nFËf ñ1Ú0´•œm¤“ µB«üüõï¿àI@'ðZþ•°ýùÏ_-þ±Ë?ÿü_~m*âE÷?ÿ%ØW} âúC°r NxæF'†‡±¦Ž0ÅB‘…ö0Ž^®saÑ3´@Yö !éþæ[oŒXó²ZpŒ¯rÄ £¨®ºmRÄ@î7žCNoÆ­¼㚃 `cDükïÇNÏžª°3¦F–²ó}¸}8ßý¦ïq÷zŠ£8뵜|Ÿ Ԗn +Š¦¡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ý6AÜ{ÎÊc—–#6}£ïëiKaŸˆÙOëüNxVM¿Ð>{’-püõ®*G–“‰²nßßù+Fa÷¹Ú@£-I˜“qY”uîyóÇ`ýõs¸æ™GWbnêÀàj]_oƒË"mè[Šd Ç‹-Lí€N26[õ¸VZƒˆ?š_©'`ò-À݈) [kò_€ö~µÓ¥/ÁÏð¦â$¯BBÎ5ßD샄,ÓnÑE+s~Î?ÎÏÆö9aBL +æ•ZNø…ú>øÊ«¦G„²bQÔô‡óû5q°éK£oRàì®5™Cƒ×ïºïû6;…5¢@©UlŠù汌p€&¸ì}jÈ}cIa8¬—ˆØ¥VŸˆf iŽ¾.p_›Òë)p±p­sì·À]âxšqì˜"‹ù퀵f´È}þ¶z·fvãê±ä ‹šÃ~=×DfsÓwF€¯ý"RU;ŽôÚËòϸu.°Ÿb¢–»¼ù ¶92¯ý''¼H;#ˆ³¤Š±´ƒÞKÅbÅ %îØí¸ÚA/¦ô2—šo«:À¿«Æ½U_è3ƒD,ЍFà¬W+ÏÎ.¬··©Îï£ñ/`ø›]¨¿ºeEö¬?|©µ¥í›Àýd~ +_w¥ W´w~ÊcF°Ñ ‚ãÒAìõ¬A‘üƒç`‹àr®ŠG¶9B¬¸8ÙØÈ šoªÕÑ©0.eþpº])ðÄ­kè”؍Y¦àhºéOAߧüÒº·ÓPªŸw°Ép:€Ù9ð\œÞ©tÀÆpÉ_øëۙúÎ$ý{Pá%<{µÀÕ/E­Õó ì%ˆoâ_œOb +¯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‚~´>÷¯Ä`¸´m?D`Ѫ™õv¦Žšè-NªT=W +pùáü;åÀ[oÔà1Ë1yÒÿAVs+â(©÷·Àœ8l¹|Zë|:&Kp’‘~4s˦;.ðPçÂò9Ùc”Øã{º‰kr™|#àîµ´ó[¿¯ÒWÐàåÅ•w¹Ö%›t®ñkjºé[¶æÃIuù¿¶ŠünN ’I•¶â>Œjp°½¼o쀌Š[éÇxªq^A¡ˆ¼û´®–P™ú½TÿÍøÐå]ÐϜÝñ¢»ü‘T†7µ~iÀïyŠB1–ZG,gÏ=ÆÈʐûçþe"Àùºkèºy«7qöøˆ› +–ÿáceø,«‹³’e°À[2]Aàç?3ëfŸG[¸†ÞU~ßg1e:7cK}0öìb«¯Güb Äúƒàô ó`ptNË~ŸÇHüÜö! d·ŒpJó5qV ˜hôðÌ%áӎØ/:Љúö­®py¥’A2Éo„§¼ß|Š'`‹Yâá†kˆ¾ºóT©fÕ§.pKòÑY¦‚L^Ì<ÕkDב¼M®Ø[9 vcýÇ®Åé͆Ø{ì©]eíeµväÛ´¯ ´ËRâ¤}¯¸•NϤ ð•^«£TûÝu_NpÚk€SN¹ïßôVwŸ‚2¥H°$`‹½T³Ÿsâ¢p÷zòÑ®ÞQÍRµrÀVˆò{Ip,û䏌c@ä•âáÖ¶܎ÆO0”,äpNtfuEW÷YZ[à‰bl_³›^?àQølÑp ÝŠUxB¼Vtëb„½MÁ$®¥·ÜÿEåÇÈwA"çr>þE½qƒ—>ÎÍÞ%Ÿhl¯¿cÁßá0\Àß â.Oe¸û^Q”›Çº—q7±¢Q¨]¨Çœ0ÁÿÓ]-+wGð ôg°Áà™é¹&+[Î"dpÙ¡(±P,¡ÞÞÕ]Õs~L‚øu)ÍÌ7—îºì’àÚÞØkÚý[ñ +²mDG1‡›2LeátIhÛ|í͏ +¤+AÔ]/X±Ñë^é šg»#k¼ +‹Á†#©Ãg™ý¹@W¿Bjÿ$8„Hë€7:yysǝ‚,^š KàpϬÄÀÞIåܶu’Í´¶5ÔÀÀb¥ÃL€Ç[¡« °0&||çZ[–aKCëᢃ†C`§V üz¸ÃôïÎÖâ`€ªI€æVPà"Ïæp ·kN¦*UÔ–¬„t³N—N^;eÞò¬ +“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²Äµé<9ÌÕlÙsþŽ³ƒä®ÓGïˆfAˆý.Ð{roÀ׺|–3†Ö¡ \ïæ»ä…Ú¬jý2nք õp$áŠe +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; <wl›³CKè–Jæ5¯£1DZmî”Ï睄mHvtÐsE*èqžÕµòåê  i™H¨je‚ðB,Uqºª D}’tÆ­T†ø4AËw• °ïål—ò´@Á Ӓw¨K4vh^âRkŸ™÷ž]OÃÏ î–Û ¸H֒ ,|~,ÐθOS¡„iÈW|k©ãÒVTÊ:XUãaìÞÀÛ{Ä%8OARì2D%WGݑU°žãê88SpÄ@jæU3҄4 qDSàúªÆ@s;Ûi¾SaËH{ ê÷ÒÓ²cØléµSeaà/)‘²ì^Ú¶ë•ÓIÜ­L¥™Xm¬»@mQO£·u_”$—ÞR4v¶¤^ï ç•¦Î UA"]Ω°‡ìŸo£^+ òŽGÝ]™¯“¶huÈ¡Ç°ë£ú”œ&í£*àÃRcƒ×õ\~øæ9°µÛ›ñÐa]?ú¥]ˆ°@ÏËqoE@ÕߗŸòì!•œã¿}$¸_Ѭ‰à2ò€X*e™˜Îµ °¤x’ô¬€{Ùµƒ±^/7ph„q¥–‰©‘×&Å83 #8ÏïvÀ+­Ìô¿¯·J-¨皐: ø ›Ñh°Í½ TèçT@:2À¶§ 6S×\Å©jYU;ŒUj+B€h¡yU1|4±¦ËgÈ9@»Å·‘ϵµ'øÑ^ì-"š7K¼.ӕ/E°®”œ¹RYWž:Ç,Ï9öøήƞÍ»*?çwƒÅNŽÆ֒7ÞÝUð¬<Ànû¬W ÑIrÖ¯!Ï4ã`\ýj&¼›8•Íî:¨±(½Ió °ÚÎÔ,xH¡¤$DWö¢e î)š#= {;¨§ª8{Sl­L\ªX‹¦e(YeÔ§h®üع +¹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*–M03”óYµ ³a1kÐbCÞƪ›ÔPa«Ó©ÄüéƚÍ]n"kö ²æ)¿ ÛÂ4 endstream endobj 25 0 obj 21776 endobj 26 0 obj << /Filter [ /FlateDecode ] /Length 27 0 R >> stream +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òYY칸—X@¯Rn;˜±[¹ŽQã4|Èε®|-™Eⱞdñâ0Ö ·Ä[|,^íà~ë¼Ò5òý¢•Å¦ºv±a;]¢(Ø$hqŽ]¶iÊgÁ¨Ýýë¡;i±ðkàèžzAb­€wÔ6>£(Á@/þ(j;¯·Ù[TU:•‰ç—rû<YúŒbó“„ށ×]W”­„´¡X׊£ÆݍyÕU¹€lÊuÅ«"J½ìsÓÏßï]X,Ò£¦ÝˆþôÎVÉe”³ÝÔaußʀ‚¬¼@‹àC¹Ț|´Fd ìÃԈ=èí|hlr)qÙî%À hJ‰t(ß Ü-wXÀ¥fûʲâ²ÐÒ¶4w e‹bÓ±y‚¥ð¶Àû±*µUû>mÜ>^(mÓÉÐJeEŸ;¹ò¬e›®‡'µs§î 5 ÛÛíkP]¨Çi_K?â5Vn´á¯u)‹Åé^r@oDy$þ7£¶haXiâºù%ôI„hluõ\F»ƒG¦[ÈÆ_s‡Ì÷á6¤ÊÖÆ¢[Ù¬e˜ X:ŽÙÍpÃKÞ+ԀÚYG4{”Á%[+#.¯ûñy`q„¦ûí·zìĄêßW(êÇöE³ƒÉÍÎÔeátйYŒP8g8Ú +OšÁ€ÞS(5 •È臿#É´Ú c®6êc·˜&ŽrÀ:{mŠÆíÃm•b´ cŒ`³ÓÈÀ26\VfG+JmÄJÒ +'Þj .ŒJ¿O––`›ÑúÖ\éÕÕ]k_¶6z5£à ®+ÔûåzÀn&üe_7sUuç!Rzůü8É´6_x_<¹sώJÓSH GC| ø.-ñeà§Tâ+g‚2>ë BôÜ僭£¥HF +˜‘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'¥gy>#å«+EW´rrQßK&6+ÊÙe¹ÿFïS a +ž0¦…<’%‰æž±å9Ë¢Âi[<ö½ˆfÜã6aî¸qhxÔ½†Æ¢(€ÿ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ûIpyÛ_çNňË9ÊÙªÏ)ûó»¯AÕjK2êžh*ÈÐd0RåkT¥QçV]çBeQÜÎÍ>c`³NUŽ+R>­‘¨Þ℠õ¹ÄD‰I]9ìÌŠÜ +*ŸÃ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ã5HcEcXBQûeQlÖ)C(²£¢ ¶8£ µ.au6söê @éT”Ûدº+#øûç&Žrì™é™ž™äŠ)²´¹Ib)wFH^ˆVDŠß>ÕÝÕßÁá cßåbY(曟þ©®ª»bu¡¡Ý¸âB?B…¹àÅðȝ>g(¿ØD@V43Nè;È‘£ÀZ4”ƒspqƒÑ™E þr`ÇÃúVa 4Üt}íäAoÚØ ¥6»`N2ÀÁpɑD[Iz†[s7$77»ù®Í0ZãGuÚƒ0wÖÏ/kôKŠÀT)/¤¶AÓýZ¦¤c>L‹|ˆ{㸒·‚™¤–5™Þ¸#¼ùô˜wÓHÚk®MpR‰aåJ- æj}sxçD7ê+ ú@¤>š“㪛SÖNÞ é9L*C8'm°—B‰Œ´» Þ°`Ü%#Oš¶Y #êÓÐh^>MAƒz;æÖ2%"„'«À&'šm p…ƶ91GŠt39™Úµ…lâ·¥pÝ9ýËCµ¼x–e¶¥õí¢)f~®Ú*—¢óXZj¶6tJ Ú1Ðú Ÿ«žmÕ\ !Á³Ðáév™ƒÑ¸žé=`c*Ðï±Gæ s0E´æv–5±‰*¾K­)›"Ù)ä얬.U™iH±–êUMx§%+Ýg’ô6gPx’Ó|¬Ü(evLÑ̨µRºc¼K´w¢¶7õ—I÷”••³ +<àÛà$mtgi€½f ( ñý¢†–è¨'¼€”E–öf¾Ü«žà Á(dø§)ö9ý—FÁå® 0Ä_® 柡…ãR”v!eÙ[€Gq§yΨlÙ1U«DËÛëÃè.øªâL„¡€hQf’°ëá¹¢‘4¦˜–ؐÛX«vS€P-²x×ݦ«S‡ÅøÏS3Z\«)†_ßUJô!ʼÁFÓ5P—™±Æfµ*(¯Jº[‘1Ø!¹­ŽcW2†ÙƵžÉ@1¥œ»®æF •=¯Ñ{ôŒ˜YΰÀ´‡á– +ª==a\Pò~Yˆ™Îh‹m¥ñÇ +¥yeD°G‚¢¾y§ë xÄ®3ŠŒ»./÷ŽŒ»¾÷óG _ëãY;à>‘ù;Â>€Çf¼¡ÞÚ"ˆöÍxïæÅÝñ œÇ®5ˆÎÝs)”žÝk”à-&føDˆáy©­jÕÄã±A õaú˜=¿M•¬W aäÕ iu0Äñ.«3Ôõs€Cu`Þ4Yáaý¦Ì†åͪÖ©šýáÌÜßV=E'ÉLßñ]39ߣð…ñ†.νvÚ*3Ù(ô)ÀàÖöê ¨C.l îgí°q,‰j€Ð*B‚™}r¥oŸd4b$ Ó5ÉhH\×îUs‰«®9õ·|Phñ¦ÈwíáÍ %\Á(X£¥™hE:­ªzXUØ RBøœ£Ì/͞תÁ¦Ú¢âZø¬ºUņ÷BÊû£|多ù©a]¶ÕÔâÀ·ûu¼A ÷X ›ƒg•ÂMñÕV‚MòFŒ< @!85§òÌ¥hûÕ´Yö¬Ö.ß oX9Ü…P컽â "³jÓqϔ Œ-)LV x ü(%LÙ.ƒÍ-õ_Ï,LµjÓKjz0re"ùŽ  ¯L•è£T[…ÙÞK„r­wvÌ9è­f(Ƚ «ô +‡Ø¡•$а!€D1ì¢výé¯Ôjه0V)ÑC~a<Æb¬†)jëæ”a†—ŠÑ&XVFiÅDkTüCrÓFµ +D¼ãý˜’¶¶dT´ +vÀÈ5ŸÖ.N¢JvCYh”9œKÎÃw­¯Û*\jÉ<»=Z1§­5;ýÍ!©6Ӆñ=ÊI…åÚjK/–ïB zA¨šÁïu”|æhqzTôùªn +eE]k§Å§°Øwp“¹Nwg€ú†^ôÒÃãÐÀ³ºõƒ\ï¶Íþyôú1úgKn÷ˆ[¡×&܅ے‘ÀŽûÐÓŒkà±£ˆr_!¿ïC×Ú¬úôýÔ*ä\¤µ ¸…‘<êŠGàŽÍÀ®Áä}°9h>Æ¿G™As“ÜœÊïK( 㼶ü^j®&“äd,3cêlóT#â­¦w'ßåŠ3À¸@-a°guùe㥤t°æ™cå„+y+¼Æ¥­ ÃZcS{¶ûLîÅÊ(žD›æ–‹c×Q8ä ]Ÿ 9€ A±ƒXŸGÇ4L—VuÖ.‘,# +‰ûœƒÙ6ZáYØ@™Ä¸ +’!N ±•QWç´bks&€Ð™¨F»RòC?± ¼Oã( úE½Ú Évmô\–úìo"!®¥Çê‚!ÀQe(–y‚£HHPpMfÑÖºøÜ÷x¾ÂÞd¼9— ü&7@§ï€ÁkÉÒÝeˆd*ÚEnCܝ@Ñ/Cl»³?àтΔÒÑE¿W2#dfž¡¨IÀn~÷ì›ÇyñþÍ»·Ï~:ýÑ ÿú÷ŸN¿|÷ý«g¿ÝÏ°üoïúñճˢ¯N°Uø¹©§ÇÿÄO9¬p¶¿ôTð·{N ²2`Ь³.šx¶EȼõƒÿfŒHϕÓUc[ü¿(ÐÙ²‡ò7Qð€c<Ã3Y¿öÓíëãµÆÁ$hÆqgWÙl<· ¾oŠ ÏÞAåóûxN0;†úYœKl?þŸ`›Sß2•|û⋏½ýÜcç#b²MÁ¤0·VÁåt{û?/ãß÷y[þÚ}ó×WÏüËó÷oþ¯Ož<þöÏ,‚¿ÿðîá>þ‹ù×ýÂð?¯o®ÖmôÝÕ¿Ú ×;çj—]mÇOvîGM~ ®5Î'Zìj+^iÛ߂è‚ãºT{ª`L5Hã{*GŸÊ€«Ù» {»“ 3Bg£=$>˜Þ)ýg³¶œâpN”‡.íâIˆ(ô¶ +›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(|òÏÔé¤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‡òݝ•ï(óê|õ.IOQJ:µQú•ÐMs oa%v¡È”H域E£‘ÓkOâ\mړ䢸zŸ¤D¥æyšeŽä&'Yó–4Ÿ¥+@g¨=E@Yeyµ½:õ?Aú ;ÔíügŽ¢'Œv­è€Ê.ߨŠé Æ+—¥Èz¾l'~ó *èRšF Ú·zæK`¨Î¤4Z»¥L+^ÓU;K +`-­Œ 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ÿ~ôêõåµ"÷ÿúüæâÍåõëåÉÍû·¯àšñóôÓîãŽ|êá/jVwN³þÍÎ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ãÞÀ¨5AG‚u3Le­Öes.ÅÎwÛK(¼ßm/<­0"3muÃó£‘Ý#’×ÚLM-a†¼ºÇ.ƒQ…ˆ³ïª2í¼ÊXg˜…<ö<¡ò†d»â|nÛ­6 ËB~ï`„£â²Ý Á/–=–@"«q÷»í]ÇÛSPÖmnðüx\÷\þÁøèðña‡„àÃSkτ&URìhwô¯öóh¢Ãh¬Cq́ÞÏ\÷tù’†~¢ì°i9ÁhD¥qLÔºðe;ùMˆ2'jÿô•ƒŠÆÑÃH´Ïö+t¨§.‹‰°§ °#vˆ‘!em&ñz݂è‘S‚¾ÎՕ󑆒¦IŒ¡/È¤à³Jq±Hñçp¨cz?aÃ0»Œ +›aúÒÓv8Tj<ÁL{ô ;—¬›ÎzÎ~ºÅ~ O7F¦úhê-6[bvlâˆÑ +ï†mÈ 9¯ q–œ‚ɮʦ‰”ցOÓ¾ßÂD‘õ`¹ñÝ&2nÅó“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”&ÜUù"љEé.;’N‚RÍ[0R£­@P™Mô¬Iû"‹bT…7+é¢J(¸5Í(”ó êÚä#%úé ]l Ýåí2Dq—ÊvC²Q€ÏÖνÎþž®^‘p\e$Õ_¶!Eò}`"Oñ'Ñ×r(€ÉÅ6¥Õ–À‘DÍƃý­/ˆ4¢glˆßږ^5XÛbd¼Ð &b¯è4·¨}§K•1td­y•I`*ÂyRD!¶÷1y8Q?Ž`S„³Bô*TŸzš@]¢Í±¥ƒ§¡U'0ÛT³šIѤË/~¸ùòòâæòýõóŸ—?ºÿèÁòœ›—ׯ—ûOÞ<ÿåÕ£w¯®_þíù͛¯¾|°üùæÎGþþù—WòПñƒÿ¹K¾÷¬ÍâïÅVS^Š_ŏC»43.™ô'ءٍAs“Dµ¨±J’•W/#{mW#ĵ¤`g£½ +{¸¬€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±é횬rDMÝ%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;!}Sª‹ç*©"°0%(-aø Z½ep‡&$Ó¥Èlƒ#&Ç6Çé]üš¼¥³Œˆ>©…a9!±ºÉÃhjÍBJŸšKôQLÅsX«›Ádæâ»v„ͯ Ol†Ùې”j +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 ;Ì øý<ì V;*… V;h,UÕÄCîypŸ)8£¼¡®¶%u‚3ÅIïã>֋gã%±{D‰ pÙíâU¯]6Ñh£ 99ž2ŒÊb {õÜ'],÷|à QNYûZdÖÝY4ñ>íÜ<«’ÊxàYœÂáï÷Ñ««Á%ö¸3«[bŒûÚ°B›¢Š:EDÂ'š}äYNdk3öpRRo­ƒ¿AòÐs =áx6lÓGžŽA¯ŒÚ4yÆ©_„5!ŽvŽ]ð{¥xV×|…ÿ‡8ùØP·ö¥·>Yšfïñ_¸ ÊÝtwhҎ©ˆrZ»ê%žÙÁ"z¨<ò$ñ²ÁŽßöJV«ä‘{›ÜÖãÝêM—dº,IéÌ:Ÿ˜ž$DüýÝ¿çP2çH¿»• Ž1l–cPס×Yû|ÌaÃ`敖þ+‚KâÁéÀœi»Åsí1'>D0¦æÒþ RåØaG¯¶¥ùŒÎñÉ_ǼF§‰ó¶|”³rhGʽx啚÷™ãÀ!„n¯ Ô9-ÄÃz˜²Ëýív‹È6?-/ 1¢nß¿TúŒGE°’µ¦Ñ ›ýUçÈÛ{Ô<&nÀVã÷ïÁvìX\™™_Ѻy¸ÑÙª7W¦rç¾%`a#½Åñå2ٗ»ÜØi¶èüÁÈ69L'¬vï›[§Ó,š4lFò^n¹ë÷9¤¯Ñ¹2¦´U‘öًŒØc5iþG“bÌM0&6öy¨ÓWî—øÞhë_$ÝµqkTtÒÁÜà)Ê(þ ²Ñ¯Yyà!Ë5^ ‹‘g#/ˆ†xr®BÜWÞ¼P¡çźˆ!R/(uâ!–›±Ú5ï³E.¸u*†@lc7®å«!ž“«½½ö39!Ë¡|n_ J¬‚ËÚU—õP¼ò½Á¹—ÄÁ8tÚ&Mœ®áéAˆñ™³‰ƒÌvð§UC™N(ä`(«•BŒ¨¶C¨öµ›-Š/?»#Ó¢cGÊ$Èð1&=êT8æɛ:PguÐJ»©s'}LÂ}Ôћ×vF*œPŋ@1¼8ŕo…"]œžcïIÕwÿè€Û1®¼—Ç…œ&ï:O<¼AigS­1ý¥«ö´±®u?îØj×wº2Z˜§å/rUEkX®zä²Ð!w ±)}qhæ’&Ϙ»µ5?òôÎö³ûv[ÄNç³4‚)NÁ¤?ô,2êNmȈ z¤†CÑc[P”ý‘§ÀòbÃjwqð‰°ûÁG™Rъ1ú Ÿ¹œ²ðõNÀ¶Â+6¹Ö<‡ÝÂqVqAeN^®™Y²ò–ËÉ+ˆ o@Òò#Í@~s¬Ð³:ŠÌö.# ÅU–5½í 1åVëÒ"©T}îè³Ø¦û’ãÐǛ>p0L†È¦…ÈwÂ$è]ͳ9GiȾì[†ê7&ÇèIôêY€g0ževÐnûM3Á£Å0룇àA¦hQ0À ¸¿KYëS°×É6 ÀãÞ"öŒ<ðì +>ÿò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?à`#µê·£Zy$ó)d½mIƐ†TÇÓØåþÄ^¼Á#f\°_“=H®L‚%Ú³TaXwV¶6¸àžo›´‚QäêJÑõq'¾‚ò©²”h°É)ü‹";»¸#ÁCþ¸3õ´×ØàÒ/Ê^Ö +ï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¬ßßiäàö84ÒëþˆƒŽ¹gQ™ÙÀ©Ö Qëłft‡¨áýì2ÈÖ#ÕaðVz¯00ÎzzÿÓ'cÉu“ŒrãSÅOýI*îl†U¼ˆòÎÏEÙkoç&ùïOÃvi¡Ìæ^āý{́äêKó +•tÒ¥w! ±²ì.y¥¸þ·t>^ȂW&r¦ï£â– ¢å{-p6‰ãq`¬Ý·+Àex›UÄ:~}ï§=õªs.ð@›Ä‘]Œ|9iu'Ãä’OgØñc›(xÁ ႸCO²G“Õ8ÿ_LäÞ¬µB‰OsRÇøØsgêؕ#û€G›0‹÷sJ&AôX½~^ª¿À·Î½4ߖ8¨s»Šý>OÌFkÕÔ÷´uQ6Å!ËvŵL+èô»ŽÔAn7žÍ…Û6»ÂÞÊ/@_ô^! Ûµì¡æ5u(ߛíö¬HÔ¢‰¥„ô<wã¬ð4ÑUUËÛÃù(ïô5‹vf|vØ]pòY덧NNàq°4Cž!L…ú²fÄ8€f9Áž)␠ÁŽ +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ã=Và±t+Á½«0Ѻ`Ø8µÀñ”ÒƒÕ½w`Öo~í8Wˆ™o¸þ +8ø@,ÚuÕ؏(wÿžMé€Ķå`h‹Þ8ëýÙ;¹³V“³òÕ¨Gø#ßc¶o¶Ñ³%¿ä×B_x¦íMð`Êw*ât½Q "àšôþYékPl•»z/|Üíºq|ÒÇßC@ú%Ÿ›ð ׃Þõ‚Ož:y6¾±Œà­úª°²no$%²„h×/Ñ«­Á5ù»(ß©Ž? ±@˜2ïÊÚÛ +=XõuÂü—Cs"¿¦iÆLæœmÆô0,·²ßô˜¥}jîMn¾¯â™Á²ý)¶QΡ±PXQ±ÎñèV±s:Øú½Ÿ–1ÀsÓ.„³9“£¸n?ÿÈÚ=]ÄØÂ8 º7E5lü&GĞ»éÒ.AŸbÎÈðˆ^ŽQ sšvUsdØÀ³[ø‚ Õ.æøLš‚À}‰âӛTQ#ö0Ÿ36­atÂ(fL'÷ûçuöÒ óïý9™Ç `òJלmag™ƒÿ²«fḺ#'ÝÃ!%tæÍ<6plÓy*ó8ÕÔû²‚sj”•‰Ð³Q¥Á§Ï¡âðZàú µvJJ½»d~nÏNg8¿O7œëÀÓ'†W4»S.Á¶ÏÏ¢ ŒšºÏí@èwŠv<±3;½+ìćæxãÒæN܁°“ºµ0ùS€ CFÍon’wúà֘,_ٕþPþîléí“F:Jg~̂ÛOäTƙaPØp⻊—É©_—WB#ø:Ùs­¹˜…K”&ÎZÀwWHºåuP~,žÏî|O¾M/fG$qzÅP1½ÒzN×]ݘ» ̎@üÜEÚì”ÐÁ靴e0ݝ§×”(ä—IèhW13øfQ§á Xæ@ =â'YŒÎÉYDø ºÂ °»fæ£Î˜ôO¸_ït:£Á׊:û$!ààCó³Øö Œ×Ýr@— KMÝáC„nmýÁ KD°¸É4ÇY ô<[ômO»øòTúM¯Íìyp3yÍŝÜÖK^]‹Éq…†Tnz‚õzÝáâ?ìØû©(›BªëÉ6?÷³9)H%´n§ݚÆ8üOz½ ÙM»̙­ð¦zÉëòÙՄ·wR-VE´3ŠÞ¥÷·zpÂÍÐMÌÝÅ¡ß"èìdN §A£>ä|ìáBÿc˜ïY~š“ðÒ+J¾*åõ“ß‹~ éqÌróó)¸Ã®û‚KU­îз}ïg X&ÜF@<ûl°ª¥êz}ˆæè¶2œÅ“>+ñ6-JæE¿û9µÀñN¾4óz–|ÀÏ*æƓé©Íïça裆Màƒ¸mÎàlT«"x0ìCŽQ¦‘’&UöÖ¸¶„ܳQ»2J  RËݔ­ÆXLlͯ| ÷sTíÓH= æi°Z;|R6#ØÚ oz_ŸË¹ô”ðîù©ÏPܤ#f*Øäàðà彆½·µxúìžWt‰ƒ\:òvú!\˜Õ˜ڜ3ô[Ö „…”¬pÌ`º_ïO?t9gËä˜e§Ó@䡌Ý\3q>^Ð?G9¾‹¸ϧ®øX+ç³²ÓQü‰«—S±wšêN92ԝšÎçtÎû†‘ý.pð}ô,,ƒ 8ª¸×"òm]ę۹.’ÑP öIé_)⌇œGžø´9mnÍ<([ÈAˆë,§Ä †ëa ƒª¤Îã&B„ñõÝÓä ºæýGë4/»[i,WY1‹èÞiXs@wOó‚Ï´ÉWÜt4榜Òû·¦;‘~œ.~%h5ßLL6Î(j´u‰—g\õìâ³îÖ¿U96?UfY9@Yœ{;ƒÒÞñ4b)ìà“š˜pj<ìÝ0֞·LèúA‡m« ã7MÂÉcÏb¯"í’:žM«žñ©$}n`ï±Öò°j[Òß²“ñU ¢œ–•¥W]Þ5ã>˜® Š7£mö÷üˆ7—'[ù.ŠÊW׊<¶)&ÇׁCŒßšíRB+X¡k—YrfÀ±DžvrB‘§)tŽ®a9íãÆLŸöf¨LÍzvWF'cÜüт[RóP“ÿ)pQ÷ï2/ÝȲ—(¾ \Ä!¶¬EÿE3ù$wóØíu†X‚V8êArïÅå­? RÔI£3>Dv°Œ§ª`ìÑs7±ƒ2mAä›0H\=ÛGÞÐË,¾+}E`¿x¬ýŸõjí­ë¨¢¿Àÿá~‰äT´™÷>9 ‘­¨AU©mµØŽÒ„*ÿžµg¯=sîñµ„®\Û+sæÎÙ³÷z¨×)âòŒc¬PØÞfhˆŽ`˾,梠í³Y­Þu–ÁVeæI€”5xG?çÞ«[BÓYº 2×$žVy(3»;»ivçJ³“k s­Î¸Øž67ȎÂܓ_Ñk@£™ƒÓ狆13§™ ËõR'ïøêè"Zw…·ûÊØͲWlŒ!$žó$V p¥a©bH“­%uÁH·ÈM[Yj9†`À!„ºˆ'ê©J®aÍ¢¾U3 ¡.^ƒZ«t›ð¢sW’åVá¢XÕ·~ƒ$Uv·*¥_†'euV½_´‘¼'m7 봔,q•D‰ +Ñî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žÇ(ɞ‡›Ôî)n¦Ö•¦µdäÙ¼¾pݎ¬SÝW¡1~MӉñˁXÈ.cfÍ-%×H0Әxó½"Ÿ!`P;ÝJvG:¹Pû£Q Ç̸>¹ãÛôeҞDǨ3æ4†Å YÇMÔÅ,tzPäAçûÒÚT‘ÈÁ³“ªt5A@üÆ:£H +ç`ªõÎää£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õÕ¦cBÒ@v2Ž\•û!~.¿ˆ[t¹;ÓiãœÐ´à Z0Îkª‘|?Æ@-©ôù8\&•/µéÈ šÛ'o`qÍXdj¤¼‹§£Ûi¢ I.9º¹k$Æ@KΌŽáj´‚h¤ãéúdº(GfPéÜÈ#®°;à7轪ºPrK£J¥Øó°LôyèbÌÏ vyeVZâXÞ4l÷fh ƒ“sR§%+TyxFG y°O΁Qe`’'6$+Vwù>-kLßD 26Ìl‡\.§·jä”ý2)ÝÒSŠmҋ\¦¶‹Ï¸z¤ÊÍ4g ½!T$Rͦb¿K6qµšº–º\åEàTÍå =z +´yO‰\ã +L“3ÑOÉqßâçZH1×Â=›ÏZþ KfWèí`ñç`îǙo°µ¸äϟß_ë¢ßáûìՙ;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•t‚ÁïIf·Ù-ð´±ëÚ¿÷ŒÁïŽáÐÅâ×Ã1Ȋý{·¶l[´G/\0Ž¡{2M®¾mÀÙø)ùÝÚyéÛ]X·“³à®ñôŒHºÝ…ÈiØmûš}€i¸ )Í:VR™4Ð6§Ö†S»†Ó'ë´pÂÇkå½ú=pWƒ‡@ÓQ/1±²Å™âð Ô `¡JÙ-õ›zoÌÎÉK˜pô'¨ÒÚëóÙ±ü #ÃT3@nà J‹LÓ_øa.ŒÃ8 §Ìâ_9dm>Ž0T7ƒg.-*QÁ7™Ãm®QƒzšÎlMùº¯¥iñòCàâ™Í2 .“¼ËÇÚf¤¦ÅºÏt€ó†éæÚÙ Ê ޓÜÿ‡–ñêß⓼Aˑ&ÓÇBG€H°ƒÄ[ùÁpüQæ?̗]oÇ †Áù{SÀš“ùþh¯d¹M4ˆSÄ@QŠ,Çn$Ù”ú÷y9äìpvÓ.ŠÀ–}Î#.w†Ã!_n``ؤp¶é˜#Ch_Qêø¸Í¦—½mFƒáb¹UsÙBϐ l‡­9µ¯] þGr+Qþ·ËŽ”Œ1öà®YMlÓ ‚˜±²óØÁùÆ* [z5è¥ +*ˬ  F‡!LÐ);ô[ÈNjáǶ‰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Ù,¹ørPkDSW€á,º©ï}0Û ‚¢£%§£T À +iüXkxg(vy÷x ýœú«|(–ËJa/½áì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€(^¾Á€ûâíì¸uÿ¢_ˆFPYÒ`„¨ i ˜ +qÐadˆFÑâ…VK7{¶ Üøe¸¥k8_ ¿,’Ðt…þh¬ïF»Í®ÛÆàëBâ$Ï3Fù2tQõ²èR¶!NÇ ÃË9T@Jn,nHòR?Ю«ÀҎÌÆ(Ï[\#ƺZç¢Ñ4ï|0¢]:JæŒÑvè½[±LÜ@)j›ǒ7!ô¨_mÂÐXMôâ@ãÕ‰–¸P+±YòÐødä-`æuI?<®ÔLûwËnèÔÒŸ6Õ°-k†c³ÿÙö#°½ª;ˆ¹%œ—.ÇT6ËZo¢ŠÖŸ´= [´Ø)á6«å­á¯án%¢”Vcæ-h|ÒöôÈÝRW!ç¨ðT>£GC¼ÈPg’‹Œ}]YË벜哸 +þèpëX90JhÞØ"cM:Ÿ=uØ¢ðõÉWg¹…†*f1Ù°ôÕ)bØ÷»RîʚQ]eu§YIaäk?Á× …pàŒ1™Už ó¬ƒË¾akÆwx.¹ejp[ÌBvû<Æ|ß=¯1ë’ "u<ëSv| +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(ØÜ®Å§Xh8kSVXë®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Ë£çoWO®¯n_}{qÿæ¯O/$“¯?jòýÃû+1:{öì¤Ù7÷—o¾÷äêõ»;±ý3~ð×.gïÕ.TèÆâžXßú%FS{7»_@‘Z¹Uh_¦P u {¦ ºÞr;”HB¿z_Ël]ý*]†_e=Ö_ ÈٚºL~Ѭãn¿u¢gÿŸ={â3(J×Ë^Æ ®ªKSMR—õªÑ‰+ýú3¯óg\æϼʿ·‹t€#d!'jÏ©Z3žU  mð’æäÅ45Ö1ËQŸšNcþk¨-«tLû+"´W¯ˆÆM»L+萣†œõ\b»m1â·XíT1õ~EÕR‡Kµ©}æy¨„Ìà#d—Z*‹·Ñd©²þ¬«¦‹2–6U›c.3ÄÌÙ9d±`ìÎJš­N¡QœK}ɲ€9béhhtP¶ˆT_²Y9ÕL½_cµÖáTíj9f6АTNÇ,°ŒFKÁa²²3HÒ0 +ó¢ÌyË°Þ¸ŽÂ°/:uíŽô\H=»äÅ®’ÐDmŠýGL^yT¨¿X¡±¾ÕÙØÆn¿S€L-T»0Nd ¾‘útÈ!KB_cT\I­“W¬^‚4 Ešç>*­»ò®Úeòê"üÈ*¹œ9R`|+>«-‚Ц"§½j¨V0á±ÚáUíkƒ9j”NÇdn#h9µÍ¡FûXƒ kJÎ+Ì[u\[ RSæË‘;yí—+Æö‚áábxE r)ýœerÐP賭ځ°X| +Ú«†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¼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}{ê@ûõÅ·Ž‰±[†@éÞÚ&%ª—©`”ö»Ñ­=ÀH\on Suƒ&Ýâ³7ò·`‡»Æ»Vhv5îٔ`ªºâýÕ&‰[O iVtЪ> ñàÏ»³®¿Ñ'9òîΚ€2šgG¨gø’v¡]º’aW£ 8‰Ê Gh;tÁ13¢»ëišïàúþ¶«aê[vl8X4®‘2]~5¼¡m§ÄJ¥ÁŠ7˜#Ö äT ŽÓ.æ1WŽŠIÂHŽåÌLH­\ eúz JÃ`¿Q¹€0Ì1Ôò+a3aˆ—»’"ì%a¹Mœ5‹–1±/½RE"<”£¹—ÁÕ38&ãô…}‚‚tÁ™:ۛ“ØfMB(!S)ù¼Í6¿PLˆ·=Æ +>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 ?Z8ºŠ(ii¯¦ÝWZØF²cÜù|YF•‘.–=—†”¨q)jÁ…bU˜euîŠÛfŠß´"áBìž×?˜QmÑm•–Lêá: ¯Ð +þüÏÖ±{ÕÇÐÿ"ܽ+gFßf1KΌ n\‹‹VÓ-Q;4µÑGL bÖÌPÊ +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¯Äâ„,ÌÝÇ·44ŽÛ©êȳT‰2btœ.ñ ~K+ãˑ3 ƒÁm÷*ƒ³µ˜€vuʽ=\ LŽ¹NZ ­È>º¾‡Å¹‘ +¢ÙŽÇåh']ؾ¹1ÁØç:¸5D#CÜh€"¦™0Õ¤lW…pmhlHrçp0ϲæ¶É`÷@zÓHŒ¶ÏKgÉ!¬ÕùJÀ\sdÅ ¿[°8êÌ&s’;Î()D¢©‘‚ÖH½µ¼ìc\/¼NÂ/ .ڀ멹+@ê’9%úfՐÅÈ®5^õ˜qN•²b”I=ÎZC#âêë’éé*Ôÿ_ŠtÈÃJ~6u7){›–Á¶s1å â¿e¼êö4 ™Ê +y‰Ë–!ʪ-þ&õÉDҋ1õ™½Û"÷.lS)Á4G“=d$¸õ Àé“ÓP+NI²B… ՃÓ×XÎîÆSÖK7‚>œ{‹¢°6®’ÕvBN —pbéÊMô "ã֚’^G–Jq¹¤9/–FÿMþS,¡ÖwdqLƒ%)#‚mu‚©¦4%Ešr͈‡Jø} +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ý¿—Ðu6 endstream endobj 27 0 obj 21845 endobj 28 0 obj << /Filter [ /FlateDecode ] /Length 29 0 R >> stream +H‰\Wݎ¥Å |‚y‡s .øÔnÛm÷%Yå"Q¤ (H(Š’"B’ „xû”ú;3ˊ™9uú×mW•m].jŸk^ÛEŸÙ¶uÙ܁ѵöâÀ<¾ÆŸOðýKÀûš²ÖÛ±[®1¶ÈþXz 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·½ixOü÷vÍ'øfÿW0Îʳ‡ú s)©`Äj̾?ãµÞ½¼…õò‰—ü¢W"»"«Ô'@äy÷ØZo±7‡:è뇍-¥ÀÇÉ9ö——ýøäÓÇW}ó×o>ÿ=¿ÿîÇo¾üõÃ?úOþí·ÿúþÇF>ùòÛÿýüÓ÷ÿø4²ÿõËËÏø7BÆîÈ×5„_ƒI¬#>俯~Ň?àúå!?>¾þÛx|‹ü9.„˜3ÊHú¡Ô.²†‘(·Jª&€L»X̝”k\<²Ææ¾x[†$ÀT LĞ-A”-Ñ.Ëw ñæYùÔQtc-2x¡R¯ËKò¨˜²À1ïú´Æ*X½öŸØäC¬1YYÎ8¨H³`µ«Jœ»ŽÙ'„¯‰DšÅª2ŸÙóQ}ž‰"ŒVNäQ×,0ó¸æïAъÙû>¿Tù +˜KÇ9@l îºuwP¦FP¼·¼§£€zÞ5Øb{ká¦Ñ E¨ŠrT ™sÇoUz€<!eî½6h£@óD@Žd“dË}¯L J»ÅL“gÃŸ 0ƒyÞ`É¥¼©½ªg´2“«âõIß \¬Åž„;>*,¨©¦Ô}(±’%G·Íî‚@¬8•†?1*3Ÿ„ŸláwÔçÐ{0qK¿«ŸÍ&QÓՁµ]gu<ÂџH˜Jä=ÈEz,’¿E˜ž©5“V€íyJ(ÐZižoÛ{I†ÁԞŽº­ùˆ%=ý" Il€¡“EMmN;6«²(ÄB÷ºµfÎÚ dÀv2î¨2ò© DifmÅQÖ~ +(©æP؉%Ôbå´4°Aq¨#[´vBmŒu˜w¥Vb)^£ÄçYó¡°ó8Ý©"À@Î|;f¢‚;àè¼+BQ•±ÇÉ`«YC†Œ=Žòmëóï;RZ)þñšÙ\{Ò[˜ÁÐÞ #ni?K*Bmvú¬X“¬0æ³?mqÓÀE,·ò$á vÜ~ÒÒJvÖèdO¨BQ +„ J ÒÒÆtvu¸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‹ çí<œZŠ}€¸HuZðt>‹;±?²ÊÊ~2÷óPè£VyÅÍãJ‹¼Ø«o}— À"WŸ˜-”"WEò·ͦ"@˜$“t `D­ê ±å‚ñ®Ò…y“šÆÁM‹;éGÒ*ãeÛWïžÞ·†/W_@8+…ãÒn÷ à,m։c,ÀÈ*@ÓKürXõ9݇S߉Nù|3êxKß}(h+´/Á0˜‰TÓ¥Üm¢åt‰IµûLKЏ}Ÿâ³?ÂkIè€yøêhÐ*pvðCpjÑUÖ¢æ^™•齟c©Æ†7‘;"Ê=Rñã~=}嶼rbq61¶û‡LÀ:U†cAñ*ý|±Ù;ƒ¥¢¥åë¡]“½gp3ñMñD@g™šÚ Y· +ôÝe w㙔3{Ö}?áDeŒTõ~íUÀU"–'Q²OØ6aV‹Àz‘P¼>€¤©˜Ñê,û¶„où‹Æfdφ[)n“ö‘… + …ûá+© 0¤-OàÑfǃUqƒráz²¬b¤ØÓÁqiŇFo4ë +Á·¥,ð¸:3 0rGßV]؊(ײX‰O;úVXRk(ëÙB®Ì€‹Íº*=ø±Ë6ʞáìì¿Òkô{}¦uºŒ3ާې“0RðrÓ\ËñdÒ2¸½m!4}~hM™„3—êW°…ç J|}ÎAN y…°Þ–ï •ö VsžÁڈF’M.s°¶pS&*ÊJ‰C¼[ž‘¬Ô>€ÛFÃÿŽ$ ¦òWïÛ±·Á ÝäÛ´!­›zˆxÝJî[‡sm‰¶âò:Ö)°ï˜»™üd6¤Ne߶‡´„¶&š¯’s?ª3nbъ!¤„¢9dsî”Ы‚ @¨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ÜÙÃܛ¹®kx˜ŒE­B‘•þˆqp±ˆpÙÊJ5GˆÜӀ÷L3Ÿª=6CŠÇÌ ¨³I¤ÖS…Õ–4(xs0™øÁñ +g‘i(æ*Ž ÞÈW|.@2ÒYÔ ©qƒºÊ!†v·à´Á ž×M5D¦:ïzÍluw1Và—6øòÁ¯<|þ#~¬$á™îœ0͇ϣ1I™H€mÁ6ù [u‚Ú•ø¢ï/8¥Ü‚œIq3»o+®)h®°ðŸ (ÑÃ=ÃæLÙ6±öîÿ£"rĕùu‰&“Õ¶}»‚°* 3@³ rÎ8îôr¬&`{ÇӉۨë9GXjP]-LÄ˓¡Änؼ,¸97ÐZD‚0Š®Å ´eç¸Ý^|¶vÍ:Ý3W(-°À:‘CCËÚã +#3:%D©°Ô\ªÉaxrŽ²fÎ~­p&ʶÚAjÅ{Ô×A“¸OˆÅ6 ÉDâovö̯¡J*Û}ÇèÃãº\‚ÿ!;ý& lý)5Ù +Gp8[ËqDZ°ÅnT“{@֊׶)Jù4’edFð*lÉùïásk…¯ž¼ÿðÍë—^ÿëí‹÷ï~Ð£¯ß}õ݇÷¯ßþíîÑw?¼x÷êë¾zûý^|øá×ß<¾û™=òÛÿøÈ?¾{Ň~‰ŸÏ "{z­Dã.º iÙjVȃ-X…³2 Í2 U$‚}­ÀÎnE¦nµ=³jWZ3‰„D"+èHìöF1¶on±gŸª ;ûKCŠ W#6¼w¬ +ö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%wFW;ÐÜ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·]†±× ŸýB”…ìœÜëÞXnòkNßìjä`™”¥WUúÊÈŽÃ 컟 ÔÁ°¶g’i>ѹ״Te§×ȼñ*OõGSR|J6Œ°Ô¡S¸Ûg·R=䅲¤Ւš†-¹ÕØ Qfe9£Zë8 RûBŒÂè?ÕFV·šì5m  O‘qN€p'鶣 NGÎiÍïmfP%¬fs0bØZV³4¿5Z QcYµÎp•²W¤ª£e¤¯,>ŠÛ=½j.˜¸ó}E 0\±ÀñJâ ¢ێ`—@òið4Þ÷ž×>Éêi5B ³{ ".„íleo™ñûͺ¤×tâ¬dH•³@ðˆ¹ÑL}ök;ˆâ1 w¢uMhÝ» Ú®êqHu‡4ӑz&4u/‡ÞŒzö?iö.nkÏãZâàX­Úz.¶iä=oF<¨2…t…ä"†O̓PrѦ²²ÂïW²/ô=åÁ¸U3‹–ì;t–«´„¿h»<ǵÉ^Ï~µZ¯ùzÏû‡éü›î*ÙÕê8ÂOpßáß ájèNV„y+¶v1(F¶±EØðöùjêóãáJˆKÑݧººê’µÛáGpȜDVM…v÷ü‘ò8óŽI‡å?Ül¦™ÛGåÎdbiûâ¬rH£ EO£QRq/"Ã+§E•v.€0~’HfN›Ã÷ɕÀuFpó¥óûŽÛ¢o§Ô-й»·(£oáÃÀù+ƒî Ì°I¾º£¤W—J`‘:[0ií‡Æá ={­…²ºL\+õœeEÞCÓÌ×Ðó²Üª`\Óf-Cj–˜ëÕgÉL§³1l%>]þ'\À{jY‚Ð-P´ mšûÛlÉr±¾…Z֝ mA‡F^!62 0—S⣽‘€F»[®®P€—M³¨Ð(¬m^ûƒ Øà¬ò4ì}hçUŒ›¥É8û|N7&ÿ1£«ë&P†{ڟ3Úш¤üfH¨Ì‹é‡ HËLÀOŠ\{ + 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+¢ËùŒviq|©¾SIå.CË¥,šéöXeӤǰŸ¸á!]\†'T¶3IHOQ˜“|w +í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ú¥£L·îb€2z†z0‹˒)pŠ°ÏC½ *VÁDºº«’ü€r»œ‰GB'ñI?µ&hf¡Ÿ†W +ØÝ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à¯¤ - y)g¯èiãä¬e)¦Èf±¤f¢¿l®ôëP3&¥4ÜÙV +^ÓÌb¥±«ªº¶Ôʲ†h¶­#ˆÓ˜@™ÇÙ¬¢Ÿ=Š=ÝîÕ¸Sáò–¦*¹ÊB[ …-­‚G¬ ¼$hÑläÑÕÖý© *2³°’CX¯ýa!ºÁù¸–†á”KV#؂la6N BçØA7¸f¡#¹ØÛ»„[]¡ņ¡öóv <øÂv°m/µ%¤e,8{¢1V¿èš§÷äÚAG*¶Ÿ-å.‹ +óÆ"³¤él€Þ]9&›W²ð0îêÛlhÉ h@5Ӛõ݌ÏUQ5.E#ªR¤ÂŸè>ö¶OW],¦z€I4b×¼{¨²ãº÷IžŽb°×j•Õpf»ŠîÓ@Èe\@<”ÀÌ +B {«lœWQ 5+;ñªs9Cö*Är1wŽlPªÃúÓÈßÐVŽ»e+qˆn¡ +ÎÆ©Ú¥»A|ùðèß_<ûðñÅ»ï?¾ûåýëŸnEèñ³'·/¾ùøáÝûÿÞóÃë_ß~ùÓÛ÷o¾~ýñ‡¿¿xrû‹-ùêO—|ûé×·±èoøóðÝC3(÷?¯>áøë̓Ü?¹½ú×Ããgo~ùÏۛí»ù ÿ{â+Ñ௾~x°!jñÓ©-ÿE_<÷ŸŒsþÝh¶óóð¨ýù€ôÿãeþ<<ªßÎÙãó•_¾\Ÿí¸_ïßæß^¶Ýþz33?©æ×&zýGüç?쟤Wô«¬]‹ÚY¿jð·?­«9gBµéG"èy©L×H:¸.Ÿô[3-ò¼¨ŒÂU^î d ÒæX[ÞAq“ó9/Mo9$MF´ÜÉ[~¸e³pf»ýç•Û!»4¦7Y²§“µ@›M¨‡J?áQâ•_簞ˆáˆJ Ãï 9Èíy–5qáÖ{IwUNl Ãcªq(b"™¿gß©7.Y 19`Hq}/‰M€~ŸïçæÊ÷C„æ›>0Û'}<ŽÊô ¸6¶»TñO‘‰²°®€L‹„hh¢~&^‡Ö֐²ðPù}1‰íØ2/ÎÅ©Ëï4§ã…Ð3Á—Xwù5!¥‰ØsöꌮÄþÙóNÆM^?;ÀüTñøêå\‰J⧅fim7aBµë‘!¦w¤oåÏ¥ ¿Þ(¸±èêÖ^€LÇýÁiÃ)K¡´Z”ÊzÖek–¯…Þ0lâùm»¸!…q‰ ‰Â˜ð ­ÐX –‘òìy1Ôz…WÏ^MÕÎì3ԙ•zºÁ0q5Ë·X¯ÄXvc—NÙ-‹Ct[B ZÅ ›EۍNN¿Æ[Õ¦ì7“g¢>ž…ƒ\»‘뼬ã&'íŽ'†ª,Ý<úÑšJòXº!•Ï§!š¬Ü3fP²Üá Ö%^p™Vn#ª²ZYύ]ށxû/[ºAîÃe?rZþy@6—Åæ»5ø<Â3 2^¶‡¡‹ãT;ޓÚhæü’¢ëVpCUôð“xíaûýÐfš ôu[‘çE_Ï®~€Q ¶æ¥Û¡x"G%ŒE5rJ7;ьG‰nÓG1­ÖwÈõÿìW[L•Ùöœ3:ü`…J­5s0ᖔÓÿ·½ÿ©:Aюàˆ25¢@ "r'¨±¾Z›Tl±5˜ \ŠÊÒ)Z±Í´™VÛ¤o>Ìt^ÛÇé‹éCû0Mj;v­½öþ9€r9S7ќ³Xìý­û·˜gK]ä÷Ĥ|,ÙAas…¿×T R„K*=„Á6¤i»I=ĚX&p]$¡œÈU´ut܀vBÀBõ¸ÒÒB¡¨œð4•)¼Špœ ‚µžz RФ´ò%@%ôM®š8êšÄå H„ws~0«‰~à>è6ÀÚñVäԎÚ{ÁA¦ô)n•&õèuõRŸÀHB9°ïqš‚ð‘QRâZh‡‚d;“°„4qõv  º ƒ†ƒ;4VæÊ3i`½qÄÀ~† 2žV¢²‰³:ƒ¬aI‘ÒY–÷rgðdôÑÃDyàυo1U¸-X(B& ô‚›LÓ¹  +”Þ<]ÇUBŽ¬W/>`‘{‚:è ™KD'ƒeÊ¡#Oé +N’ô q6ðÞBºB(Î s8 rפÁ¥4°qó¢ÖÂq +“àšæÀz㘚¦+ ÞBøUu<˜H[ùÕ6-5´ !]u­ãªñ£‚ÓCÒXN}æ“P÷a =%< +M=‰8Ž*_ -=I0‹<[]À˜¥S‹ê ؎-.l1¦G)§©+ M᷅JBxˆÞTÀ‹t^ÀŠ ùn@Å,éU ·TE…MÍ,üµ¯ˆn¼´ =ðMSÓ&Ï']Üt¨´@hùê!ä>šŠ ?@e»š¶ GF¬7aü+&ä:ÊzIåꍝªÅt ­| +*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<‹é¦â˜ª/ËD£ `Câ2}eÉPùsì%2ÿ¸G¿&M¹€Ja\_ʼn ¦Ln;0@Öõ~1=­ý†-,ŠV€Oy%»˜yxGûÑwÏ?r¢M}ßÖÐØÜ®$…šÚê›êš;¢%õõ 'Of´þU1NË–¼ß*p¶¸ÀšáM¦šðÓ +r&?öYøRZ@t&êD߉VךѣxQ%ZìImh’ô[ª®&ÄÀÐ5kReÒ*s\Ú[L$ +q2bíH$ì8én)"gJÏ€Ð×%</Ô°èÚ@,ù‰§ïCpAWaÀ},â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Äìښ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³Šþ`çûãx864zot óضÜç h&›+Ûºû¯u¶ìÛ235§‚M[Êöì)ۜŸ“™€r ¯2²°%¤ioΨš¹ùçåíh?ZZwªÎ¨zÛÈ+Ùå†ï•u'O5t;ÓðcF+áý”Ã'¦>W5¬˜É„=cs߇/ܵ¢Âòœ˜o 7złÇ<Ï©cÇ|Óµ£m(åVŒÛ¾Mb×ã‰=³©mÇlá{JêÄ,W*ÛNŒ;œø]ƒEKÞ4¶VÌYBØG¸1O˜á¸1ßõ˜‚3!¶cÜQ‚蚶ƒ2ÂåRèǸËâdõÒHó}²¦oǙ5éÊ8™zþ|B,b–é°hüÜŒ™à¼I¯kY½1 h¼ê„EwN·¾Þ8ž—1ð¼É‚˜Y\È0ˆg® 'ùžŽ™ÞÿدÖå´u-ü¼ƒþdÌ#Ë7Üþ*¤éîîiÉ@ºwÏt:pclÆ6ͦO–$Û`C’ž$\Œ´$­o]¾µD4¡%h¤®ºNƒ`£8¨YŠn& ³A,7åƒàCKçªC!ºÚ]ÃZ6&A¢¶i$-âÂҋ[æ³ÃsƒmÅÔ,+‘ÔÛV›‚n–´ Âa;VÚ1P¢êìºõ6ŒÏÝQì> è5 aÔúàǨþöƒŠÑ(˜Î‚¹ï hBg M‡5Ð+zïBF€5n½614Eǚ&­kµÁ‹†žæáü¥f£z}ý§ôéìzï Y&&JòLŒ—eš˜(É59^’mg×Ü2"ã¸5zâ3Í:xNóNjR’y«{çž@öÉ È¿ÔÊdàÙõPXN˜q™‡©_×2QN<<åú²QB~x>Ê<(#eä–“g×ˬ”ˆJòRªZ’™R/Pnê_|ŸN™ƒ,c-Oϸ¼rÓaà- éõçÀ¿ ]?výq³)‡;lìúù‰ÚçŸi˙KoŸ½á6Škõ·N0d¨Σ úD}:f!ê… Û琜ìRÏsÇ!MÜQ"y^ ih£>·Yk‹h5ku!orù Æh›¼€lï'õò¢b«ry’Êß¿7ÄÄ,[Iåï_aàTóTt0¢1t7BJl•ŠvÀ†>Eÿaô¦L6ëSÇ¥zƒ˜†99±ÕšðŸó)õÑc¥Ç§RÞâu@Ìc-$¶Ú,ýÁ¿Eƒ™߶);[…Á,„Dêù—4žD/™?r½í2]i+ŽzèÅÜa[%¤Ï!•AÉí{½§ýw±YF†(Faày +ú›†.z \éãIºøRØÈ/92µË­šðŒÎi4DÄapËî_Þ£·+Bb«2CÞÂgÄÏý»v=EîHl•Y|³ü€NØ·É&¤À%s҆ŒîÉ€ÿ€û‚j#Õ/ ‹7‘ÍÃ6UvrF%âh ©Ò þâëXÁJ !IÈé€Ë÷1Îx<±µ±<¶ _äx'gÄ*N¼¦„ +}¼ð‘ŽÍ +‘<µ¹µáe‹×h«L‚uÞ­ruDèØJ)•d0R¡8!hámB7í&VT‹Ðbµ8Eƒ·þ䏒1¹ƒ¥aUÆú.hfthòåÄ­]Š¢P“¶âÐ%£ ݜx0u ›ð kªfi ËÎã€HU4ÈQÔTXDüà‘Ëß\ªJHY1–x>³»% + âŸ+,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—ëxÌw@&ÖgÐðN`2Ùò únQ]FØV݉xöza"{)–Áé'žRÑÇ¢§T§8²ŽK<•ÛtG§tߙ»ñP»Y§bǦœR¡ck­wÿ²Ñœ« &ÄÚUNéxs†ÞÁS ’ÓÇß®Üpºˆ;:1îE‡á’J;°v=- ã:.ócd ~ÉÕTLø7±I5 ¢ºTEµu+9Q~vÆ°a§s8Ïn1™l"ÎÃ`†êw qxf²za®%ë6̲$Væ¯ÜØ»7´ ýé3 +šO‚ðW#?ð“…1€Žå¬>ô|§ 0ôæ¡À8rhx›¬‘ÁŒŽ’i uÃcOÚ£ÏÓuƒÐga´f‘•Ù›¬JìdÕHðx™GS4w ùê¹µ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³}¯³öQ©º} ª~º—±²˜J@ c8C\½TÝ0y*µñéï^¿I¾œP?¦Ñ”ŽƒÐ=…:|zr~`o÷ƽûçpÊ˝ Oç¿ÝÀƒÎæݬÕäeÐ^~é†B HaEǖ}œL>júªêqÉù7Œ†urQMËäßÄ0Û¨icÅ´14‡Šª¶Ÿc\…ןUÑn$[¨Çqßï_ ûèNÌP÷Ó?¾Ôç—ú\-#«ÄT [åSØd;ûȉ|Ôì5^Êó¡å™X/ðHu¸Þ¥Ó`~ãÑ1k< ÿ¿åÜñÍöuýK ¾¿Ï£Ø¡Kæ?…ú[ۜ h÷k)]|,IsÝuêá%ü­ êÍcÏõ9@¾¾!ŽÏ Õ{77\E/i<^zó±ëÃ,K|[_]Þºrcï^ì¬]?˜'<˜VÔZŽ—¨”Íí¤‹†Z}F!}#÷—0ž81õ’—!&ŸEUÓQ:Œo³tb¹!úµ?ìÞY“KØH¾ãbŒBw8ôè‚ÞU͍•9Æj樸$udDSN {d~b0†(¸ÿêŽÑÛ5(e"%¨–b,…öƒ7 B‘ÇˁŸ,Œ +è¨?š\¿ø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ú±nPÓã"Ë›c L\5dÇ&¡Šm[ÑJ°VÊ)ÖL°VÊ)vßñ¶Ë‰GÖðc¾–·8?» /Z~FÎiGa”7ÞôèvåI±Ü\â,°"Èeð ‰üî((O.§³™mC/ýêþßÏn—wÛýzh}žº$~~€ÍË8Ï¡×ù +§§[ý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‚“> stream +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>Ì=«ijY"2n)°eª„hjgåÀF±!#ZZ±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‘?lòËõh²Þ>Ç°%€ T¿ƒ5Kf±Sk½'ë:>ØâßOÀú6sÌ= ;爈èHüí~e.s œJÔåpf&˜¥¶-a„S<’è±2¯jya?¤D¾¿¸°{K*3–>»iVþDîÌ¿Ü.pnüÚ2h «åP†WóýäAF‡*Nj8Þ§¿± @;´ë½JÈ®\ÙpJ€Ý:¼TU¬6eÎ3qwë àr~xÚ4ûŸÙa/8ä—eÏ$•µGO/Û QÏù¿8þ”‹¹Cºòvyš–z~'EՇÓÃéñu›Ûâ'c±NƋçŠÙN)M«]k_4OLQJ~UՌxÓÙî ¶eYµ‹öùùÉÒìWuÙLÀ…°GžÄ÷ԗé.…¨Þeìp¤­ +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¢Ùå~ЅM©¤3­É ½Ú{L½hiÊÇÑs* ­¹„ØÍoá;ԉËxéÌJ¹iî,=A* /\áNæÄP S8«Y,¹°XP/ H/b?d$8{֍om;Ô ¡Ø~HÉè´)¤mYý’GÈIDøœ 6'<„}I¤ÍBŸÜ&p¹iýCFÅõȜÐ%2ϝ»æ°åA6¢ß&n‡Ò[!ߑÅ6ì(¶#"²K({)ä;)Ï&ôP¶L\—g•Ç€³'’íÈ3ϙ-#×\ß^A–auۓé؆•@Hr”5ô¥Tå0îwµÁQýž‰’Ù*¾ˆË …¼@՚EžXóò_§#‡ð +º`—«‹\Gà3¹ \ív6ŽŽ`FùK{k҅– +zxA¸a¡äτ?ù{v,Ø£­ Zr¶g…EVõ‚äìkÎÒ÷w× <ÁN{™téÐ`ªj‚ÃDø÷øbÄ°|'„çÃDÉß³a¦l±€Á[xø¦^PõѧÏ&r¥óÐÈî™&ò•{Oºê[Z/·¯›“è'µþ˜Z}£vçCû¢ÖlžŸÁÌXRύóVÍl¦V?iTÏ({éqòÛ+ùöŒÀ æÉYÍj´ÐX*ó‚~ ôÃg—ÊüãF}ҋÏÅo`ƒÿóÕÚµ‘Eþƒ¾lÕPµÓé÷#û A l&Y* Ëd‹*—± ¦°1ñ#@~ýޖÔOµ<Š-QIÊœô=ê¾ç>¡o6gǵ†×ì Üý­%¸ªlŠÙŸíŸöï×mftˆýÙÝ´ª‹<=ûúéÔæÝõ÷uøó/P—¬'vßÞYGn³¿þ[Am%õîMÕݎÀ‡£õî¤Ú5-¯xä¿Oí^UöiOûmß[žÃVU +ÿ²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£QRn!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Ÿ ¡_HAð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Ø!^g4fSÅ©J%ŸJEº¦²ã†€&ª'tt\Έ›)ÄÑ —Sæ|J +‚"†!Á™‰¼ë/€3ó+B0I¾•J£,S0gäç?F§™_arêfuôÊåT! în¢mò †(5"Ú=¶IoX€æ_0‰†â¯%êX0°´cqn.§ÆJUlœ»™ÆÍ¿Á‚I$¬P>É#‚±—Æò8(Ò ä‘<ýZ*‡ÍV:4€2EE’ˆñ‚6àn¦qÓo O7 ++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úLqâfq䃥‰*VºzÐ|;iëSo4öÖ«¿˜åÉ]9 Ð3E2;Ë +Èa4t>iÆò#'n&GXpî" +I¦âý±Ý<ŠC|}òVCdî’eTz›DWNÅð¸±ë€„9&INÜL!æ˗¬t+áí$LbÍA›u÷ƒÝ%…‚á}T!Ð(¶ •P’Þ(–…Ày +¹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œ¡˜ÍOmdi+hŠØ¨VH›Î-h«v€ºmKH H¯қY.F y&"®yKʑ†•˜KÙ$æñ½ä,9¬RZ^B".™†ÂX)£P k §ÒÅñåy<0aaŠx¼øž( Á.íŠ)¸¼X&býB?õ..A½aÂF0†œ€¯žÇ˜-…0òي ‘Àr ëmcß· 5<ò~ÔTœ“ŠPoØz®Þ^T®—¸×{×ÛOP=·ÀW3}Ğ{Àû.3ÏJh¨{QÔ¬¶Ï+˜àª^s¤•¨·Çö›—ðë?¾ˆ¨¾;Øܽ?>|zyøñødÝ\œ×j¹ýYSXŒ ;mßÂl™Síû²ÃjÕ<¨·_Ú°ýP­^_Â_wuµí¨wǕ¬WêÝ«Ô­?C¤?tÕV8À?93 €rÆÑûÀL˜/oõÁêÉ]ÓÕ~:|LÒìc š™Àß=ãðú)ö<æé]“ !âm{NÔÏäNoªÜ~3ÚВ.Û¿ +æU²çU?ÞùUqfFÏÊÊßy)öº­¶¯"6û$ßó¬îò,ßއõÄޅabÏ#>ÖrëM6.(÷¦ˆ§ƒ9TïK¦Ÿî^|ñ5…tr !ä“?ìª/A¡‘äLN)ƒ°2rϳßïYÖî¹yöD'؟ŽßU€zÃASÒ^/cGŸ=`õèN1è怂bÙ@`%óP$Аb“f8^xÙ(FŽîyÜÏ÷z\¡vƒ®ŠË¡ƒBñln™£Õ° +º·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šyqÇÊ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²â‹—êƐ‘ljd6'Ãb~çn ì󻐄hkaiã°Ã§¡+´™ï|Íí-·ã·)þ/9÷}{Bˆ¤ö˜¸¨üûnò›¿à¾¬5é×p:ú.þı_Ø㜀ÇÔwωH|ÿÜo®/·çãͻ˕‚p?çî+Mà<æ9o|ÈL‰\’¶°Œ]tUÛBº©HÂ[`Yéd¨Û?Ä#€±߯‘X§p_ɳ“ûs›PDȂSº@$Z•÷²1MÐ4NÎÌó§÷ñ»¦ï´Ñ,>™E`%o˜ŒB<°£õL±D%Ñ-!žD×ùéAzâQ]¯†!'ž£nÇy·$þàKуÌK*‡A‘{ +%e¡ÔáÑ®Ff¥þ§oƒð¶ôcù2ب«Î¨ fy”ú \‹\“(O—>À'zªš¸Úq¿’ !BûÁ®Ffgô²% ˆRr(W'VX~¡l ƒr†™ÊZçám‡¡Ó X'ë—;\ ]Ÿ~XçTíÀD=Hó-Œ&ÅO›¤Z8"öXá%6ô¦df†¯Ÿ?dVú‹ôA}°K±òƒáåà× ݤkL†K%¢ÉšŽßˆ¡K*>/ ¹ú,ýRFA\1X4yã°°¯YÛ¤ŸÚé¸Èéé™gVâ+'ñT½;æ¾ùt¬b‘z€®g0f¨Ç¥È +3C>MH•}+/ +>tVÍらNö¢½56 ÿJ_;ÕF€)o½lB Šƒ©N +1ÐÙØÛNj ³1BdFÃ4;?¹<@5eËu‹5²vhç†ŠÉžÇ…ž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`ûâ´ñi±Zåi9êv\àV`¼_KSŸ8}ÍÚME£ˆªuÌ“&2D_Gø‘e+ "Ä^bÚe½—6Øûˆi +†ÞÝ·ÐƒÄ 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ë<ÐæïÊuΚwxzŽaé5E[,´Aüí$&EÛ&˱à't㉅[[òSîÓº/GMÖgµ›uyC[ßÐÆÒ½ßs7úî¬ýÿÜ¿ö…‚•S'`>ö0h‰CÛ2X•Ï*¸Oڼ܋ ¥É‡²½ Ø +sh–\uB‹æÒU;9Ew²RÚ_ÔLŸâ>зª—¡u‚¦ÑI24l@Éð³eDù×ò¤Àk„6MéêÞ*{<1¥ïq©ïç Óãš@[F[EôŒ,A[%B¤5‚]Ì/> endobj 35 0 obj << /Type /Encoding /BaseEncoding /MacRomanEncoding /Differences [ 219 /Euro ] >> endobj 36 0 obj << /Type /FontDescriptor /Ascent 1169 /CapHeight 723 /Descent -515 /Flags 32 /FontBBox [ -790 -515 1665 1169 ] /FontName /EMKARY+LucidaGrande-Bold /ItalicAngle 0 /StemV 144 /XHeight 536 /FontFile2 406 0 R >> endobj 37 0 obj << /Type /Font /BaseFont /EMKARY+LucidaFax-Italic /Subtype /TrueType /Encoding 38 0 R /FontDescriptor 39 0 R /Widths [ 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 316 289 374 632 632 699 728 229 334 334 482 632 316 337 316 482 632 632 632 632 632 632 632 632 632 632 316 316 632 632 632 495 870 682 602 715 765 584 546 757 771 331 416 673 566 861 759 783 574 783 668 546 632 723 679 900 644 606 657 334 482 334 632 500 584 632 632 511 632 542 403 624 655 317 367 596 318 968 655 584 632 621 494 464 379 655 581 826 533 581 617 334 253 334 632 500 682 682 715 584 759 783 723 632 632 632 632 632 632 511 542 542 542 542 317 317 317 317 655 584 584 584 584 584 655 655 655 655 632 280 632 632 632 632 632 601 632 871 900 584 584 500 924 783 500 632 500 500 632 655 500 500 500 500 500 428 428 500 902 584 495 289 632 500 632 500 500 545 545 1000 500 682 682 783 1021 914 500 1000 484 484 316 316 632 500 581 606 84 633 349 349 500 500 632 632 316 484 1048 682 584 682 584 584 331 331 331 331 783 783 500 783 723 723 723 317 584 584 584 584 584 584 584 584 584 584 ] /FirstChar 0 /LastChar 255 >> endobj 38 0 obj << /Type /Encoding /BaseEncoding /MacRomanEncoding /Differences [ 65 /A 219 /Euro ] >> endobj 39 0 obj << /Type /FontDescriptor /Ascent 928 /CapHeight 723 /Descent -205 /Flags 98 /FontBBox [ -265 -205 1098 928 ] /FontName /EMKARY+LucidaFax-Italic /ItalicAngle -12 /StemV 100 /XHeight 530 /FontFile2 404 0 R >> endobj 40 0 obj << /Type /ExtGState /ca 0.5 /CA 0.5 /BM /Multiply /AIS false >> endobj 41 0 obj << /Type /Pattern /PatternType 2 /Shading 42 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 42 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 308.97852 86.45752 308.97852 72.28418 ] /Extend [ true true ] /Function 43 0 R >> endobj 43 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 44 0 R /Functions 45 0 R /Bounds 46 0 R >> endobj 44 0 obj [ 1 0 ] endobj 45 0 obj [ 47 0 R ] endobj 46 0 obj [ ] endobj 47 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 48 0 obj /Pattern endobj 50 0 obj << /Type /Pattern /PatternType 2 /Shading 51 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 51 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 327.40234 86.45654 327.40234 72.2832 ] /Extend [ true true ] /Function 52 0 R >> endobj 52 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 53 0 R /Functions 54 0 R /Bounds 55 0 R >> endobj 53 0 obj [ 1 0 ] endobj 54 0 obj [ 56 0 R ] endobj 55 0 obj [ ] endobj 56 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 58 0 obj << /Type /Pattern /PatternType 2 /Shading 59 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 59 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 296.93066 72.28418 296.93066 86.45752 ] /Extend [ true true ] /Function 60 0 R >> endobj 60 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 61 0 R /Functions 62 0 R /Bounds 63 0 R >> endobj 61 0 obj [ 1 0 ] endobj 62 0 obj [ 64 0 R ] endobj 63 0 obj [ ] endobj 64 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 66 0 obj << /Type /Pattern /PatternType 2 /Shading 67 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 67 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 318.19141 72.28369 318.19141 86.45703 ] /Extend [ true true ] /Function 68 0 R >> endobj 68 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 69 0 R /Functions 70 0 R /Bounds 71 0 R >> endobj 69 0 obj [ 1 0 ] endobj 70 0 obj [ 72 0 R ] endobj 71 0 obj [ ] endobj 72 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 74 0 obj << /Type /Pattern /PatternType 2 /Shading 75 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 75 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 306.14453 80.78809 306.14453 94.96143 ] /Extend [ true true ] /Function 76 0 R >> endobj 76 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 77 0 R /Functions 78 0 R /Bounds 79 0 R >> endobj 77 0 obj [ 1 0 ] endobj 78 0 obj [ 80 0 R ] endobj 79 0 obj [ ] endobj 80 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 82 0 obj << /Type /Pattern /PatternType 2 /Shading 83 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 83 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 328.11133 89.29199 328.11133 103.46533 ] /Extend [ true true ] /Function 84 0 R >> endobj 84 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 85 0 R /Functions 86 0 R /Bounds 87 0 R >> endobj 85 0 obj [ 1 0 ] endobj 86 0 obj [ 88 0 R ] endobj 87 0 obj [ ] endobj 88 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 90 0 obj << /Type /Pattern /PatternType 2 /Shading 91 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 91 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 394.01758 72.28369 394.01758 86.45703 ] /Extend [ true true ] /Function 92 0 R >> endobj 92 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 93 0 R /Functions 94 0 R /Bounds 95 0 R >> endobj 93 0 obj [ 1 0 ] endobj 94 0 obj [ 96 0 R ] endobj 95 0 obj [ ] endobj 96 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.19609 0.86269 0.93329 0.0667 ] /C1 [ 0 0 0 0 ] >> endobj 98 0 obj << /Type /Pattern /PatternType 2 /Shading 99 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 99 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 43.93799 86.45703 43.93799 72.28369 ] /Extend [ true true ] /Function 100 0 R >> endobj 100 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 101 0 R /Functions 102 0 R /Bounds 103 0 R >> endobj 101 0 obj [ 1 0 ] endobj 102 0 obj [ 104 0 R ] endobj 103 0 obj [ ] endobj 104 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 106 0 obj << /Type /Pattern /PatternType 2 /Shading 107 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 107 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 72.28418 86.45703 72.28418 72.28369 ] /Extend [ true true ] /Function 108 0 R >> endobj 108 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 109 0 R /Functions 110 0 R /Bounds 111 0 R >> endobj 109 0 obj [ 1 0 ] endobj 110 0 obj [ 112 0 R ] endobj 111 0 obj [ ] endobj 112 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 114 0 obj << /Type /Pattern /PatternType 2 /Shading 115 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 115 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 96.37842 86.45703 96.37842 72.28369 ] /Extend [ true true ] /Function 116 0 R >> endobj 116 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 117 0 R /Functions 118 0 R /Bounds 119 0 R >> endobj 117 0 obj [ 1 0 ] endobj 118 0 obj [ 120 0 R ] endobj 119 0 obj [ ] endobj 120 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 122 0 obj << /Type /Pattern /PatternType 2 /Shading 123 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 123 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 136.06396 86.45654 136.06396 72.2832 ] /Extend [ true true ] /Function 124 0 R >> endobj 124 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 125 0 R /Functions 126 0 R /Bounds 127 0 R >> endobj 125 0 obj [ 1 0 ] endobj 126 0 obj [ 128 0 R ] endobj 127 0 obj [ ] endobj 128 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 130 0 obj << /Type /Pattern /PatternType 2 /Shading 131 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 131 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 160.15869 86.45654 160.15869 72.2832 ] /Extend [ true true ] /Function 132 0 R >> endobj 132 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 133 0 R /Functions 134 0 R /Bounds 135 0 R >> endobj 133 0 obj [ 1 0 ] endobj 134 0 obj [ 136 0 R ] endobj 135 0 obj [ ] endobj 136 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 138 0 obj << /Type /Pattern /PatternType 2 /Shading 139 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 139 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 284.88379 86.45703 284.88379 72.28369 ] /Extend [ true true ] /Function 140 0 R >> endobj 140 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 141 0 R /Functions 142 0 R /Bounds 143 0 R >> endobj 141 0 obj [ 1 0 ] endobj 142 0 obj [ 144 0 R ] endobj 143 0 obj [ ] endobj 144 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 146 0 obj << /Type /Pattern /PatternType 2 /Shading 147 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 147 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 29.76416 72.2832 29.76416 86.45654 ] /Extend [ true true ] /Function 148 0 R >> endobj 148 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 149 0 R /Functions 150 0 R /Bounds 151 0 R >> endobj 149 0 obj [ 1 0 ] endobj 150 0 obj [ 152 0 R ] endobj 151 0 obj [ ] endobj 152 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8745 0 0.00389 0 ] /C1 [ 0 0 0 0 ] >> endobj 154 0 obj << /Type /Pattern /PatternType 2 /Shading 155 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 155 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 60.23682 72.28369 60.23682 86.45703 ] /Extend [ true true ] /Function 156 0 R >> endobj 156 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 157 0 R /Functions 158 0 R /Bounds 159 0 R >> endobj 157 0 obj [ 1 0 ] endobj 158 0 obj [ 160 0 R ] endobj 159 0 obj [ ] endobj 160 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 162 0 obj << /Type /Pattern /PatternType 2 /Shading 163 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 163 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 72.28418 80.78711 72.28418 94.96045 ] /Extend [ true true ] /Function 164 0 R >> endobj 164 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 165 0 R /Functions 166 0 R /Bounds 167 0 R >> endobj 165 0 obj [ 1 0 ] endobj 166 0 obj [ 168 0 R ] endobj 167 0 obj [ ] endobj 168 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 170 0 obj << /Type /Pattern /PatternType 2 /Shading 171 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 171 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 84.33057 72.2832 84.33057 86.45654 ] /Extend [ true true ] /Function 172 0 R >> endobj 172 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 173 0 R /Functions 174 0 R /Bounds 175 0 R >> endobj 173 0 obj [ 1 0 ] endobj 174 0 obj [ 176 0 R ] endobj 175 0 obj [ ] endobj 176 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 178 0 obj << /Type /Pattern /PatternType 2 /Shading 179 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 179 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 108.42529 72.28369 108.42529 86.45703 ] /Extend [ true true ] /Function 180 0 R >> endobj 180 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 181 0 R /Functions 182 0 R /Bounds 183 0 R >> endobj 181 0 obj [ 1 0 ] endobj 182 0 obj [ 184 0 R ] endobj 183 0 obj [ ] endobj 184 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 186 0 obj << /Type /Pattern /PatternType 2 /Shading 187 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 187 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 128.26904 72.28418 128.26904 86.45752 ] /Extend [ true true ] /Function 188 0 R >> endobj 188 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 189 0 R /Functions 190 0 R /Bounds 191 0 R >> endobj 189 0 obj [ 1 0 ] endobj 190 0 obj [ 192 0 R ] endobj 191 0 obj [ ] endobj 192 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 194 0 obj << /Type /Pattern /PatternType 2 /Shading 195 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 195 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 116.22168 80.78711 116.22168 94.96045 ] /Extend [ true true ] /Function 196 0 R >> endobj 196 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 197 0 R /Functions 198 0 R /Bounds 199 0 R >> endobj 197 0 obj [ 1 0 ] endobj 198 0 obj [ 200 0 R ] endobj 199 0 obj [ ] endobj 200 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 202 0 obj << /Type /Pattern /PatternType 2 /Shading 203 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 203 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 92.12744 89.2915 92.12744 103.46484 ] /Extend [ true true ] /Function 204 0 R >> endobj 204 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 205 0 R /Functions 206 0 R /Bounds 207 0 R >> endobj 205 0 obj [ 1 0 ] endobj 206 0 obj [ 208 0 R ] endobj 207 0 obj [ ] endobj 208 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.21959 0.93729 0.01959 0.00389 ] /C1 [ 0 0 0 0 ] >> endobj 210 0 obj << /Type /Pattern /PatternType 2 /Shading 211 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 211 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 174.33154 72.2832 174.33154 86.45654 ] /Extend [ true true ] /Function 212 0 R >> endobj 212 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 213 0 R /Functions 214 0 R /Bounds 215 0 R >> endobj 213 0 obj [ 1 0 ] endobj 214 0 obj [ 216 0 R ] endobj 215 0 obj [ ] endobj 216 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 218 0 obj << /Type /Pattern /PatternType 2 /Shading 219 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 219 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 148.11084 72.2832 148.11084 86.45654 ] /Extend [ true true ] /Function 220 0 R >> endobj 220 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 221 0 R /Functions 222 0 R /Bounds 223 0 R >> endobj 221 0 obj [ 1 0 ] endobj 222 0 obj [ 224 0 R ] endobj 223 0 obj [ ] endobj 224 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 226 0 obj << /Type /Pattern /PatternType 2 /Shading 227 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 227 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 162.28564 80.7876 162.28564 94.96094 ] /Extend [ true true ] /Function 228 0 R >> endobj 228 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 229 0 R /Functions 230 0 R /Bounds 231 0 R >> endobj 229 0 obj [ 1 0 ] endobj 230 0 obj [ 232 0 R ] endobj 231 0 obj [ ] endobj 232 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 234 0 obj << /Type /Pattern /PatternType 2 /Shading 235 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 235 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 210.47461 89.29102 210.47461 103.46436 ] /Extend [ true true ] /Function 236 0 R >> endobj 236 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 237 0 R /Functions 238 0 R /Bounds 239 0 R >> endobj 237 0 obj [ 1 0 ] endobj 238 0 obj [ 240 0 R ] endobj 239 0 obj [ ] endobj 240 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 241 0 obj << /Type /ExtGState /CA 1 /ca 1 /AIS false /BM /Compatible >> endobj 243 0 obj << /Type /Pattern /PatternType 2 /Shading 244 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 244 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 188.50439 86.45654 188.50439 72.2832 ] /Extend [ true true ] /Function 245 0 R >> endobj 245 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 246 0 R /Functions 247 0 R /Bounds 248 0 R >> endobj 246 0 obj [ 1 0 ] endobj 247 0 obj [ 249 0 R ] endobj 248 0 obj [ ] endobj 249 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 251 0 obj << /Type /Pattern /PatternType 2 /Shading 252 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 252 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 274.96387 72.28369 274.96387 86.45703 ] /Extend [ true true ] /Function 253 0 R >> endobj 253 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 254 0 R /Functions 255 0 R /Bounds 256 0 R >> endobj 254 0 obj [ 1 0 ] endobj 255 0 obj [ 257 0 R ] endobj 256 0 obj [ ] endobj 257 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 258 0 obj << /Type /Font /BaseFont /FUUGFW+LucidaSans-Typewriter /Subtype /TrueType /Encoding 259 0 R /FontDescriptor 260 0 R /Widths [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 750 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 750 603 603 750 603 750 750 603 603 750 750 750 750 750 603 603 750 603 603 603 603 603 750 603 750 750 603 603 603 750 603 603 603 603 603 603 603 603 603 603 603 603 750 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 750 603 603 603 603 603 603 603 603 603 603 603 603 603 603 603 ] /FirstChar 0 /LastChar 255 >> endobj 259 0 obj << /Type /Encoding /BaseEncoding /MacRomanEncoding /Differences [ 65 /A 219 /Euro ] >> endobj 260 0 obj << /Type /FontDescriptor /Ascent 928 /CapHeight 723 /Descent -205 /Flags 34 /FontBBox [ 0 -205 625 928 ] /FontName /FUUGFW+LucidaSans-Typewriter /ItalicAngle 0 /StemV 96 /XHeight 530 /FontFile2 402 0 R >> endobj 262 0 obj << /Type /Pattern /PatternType 2 /Shading 263 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 263 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 19.84326 86.45654 19.84326 72.2832 ] /Extend [ true true ] /Function 264 0 R >> endobj 264 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 265 0 R /Functions 266 0 R /Bounds 267 0 R >> endobj 265 0 obj [ 1 0 ] endobj 266 0 obj [ 268 0 R ] endobj 267 0 obj [ ] endobj 268 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 270 0 obj << /Type /Pattern /PatternType 2 /Shading 271 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 271 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 9.92139 72.2832 9.92139 86.45654 ] /Extend [ true true ] /Function 272 0 R >> endobj 272 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 273 0 R /Functions 274 0 R /Bounds 275 0 R >> endobj 273 0 obj [ 1 0 ] endobj 274 0 obj [ 276 0 R ] endobj 275 0 obj [ ] endobj 276 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8745 0 0.00389 0 ] /C1 [ 0 0 0 0 ] >> endobj 278 0 obj << /Type /Pattern /PatternType 2 /Shading 279 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 279 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 19.84326 80.78711 19.84326 94.96045 ] /Extend [ true true ] /Function 280 0 R >> endobj 280 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 281 0 R /Functions 282 0 R /Bounds 283 0 R >> endobj 281 0 obj [ 1 0 ] endobj 282 0 obj [ 284 0 R ] endobj 283 0 obj [ ] endobj 284 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8745 0 0.00389 0 ] /C1 [ 0 0 0 0 ] >> endobj 286 0 obj << /Type /Pattern /PatternType 2 /Shading 287 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 287 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 120.47314 86.45703 120.47314 72.28369 ] /Extend [ true true ] /Function 288 0 R >> endobj 288 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 289 0 R /Functions 290 0 R /Bounds 291 0 R >> endobj 289 0 obj [ 1 0 ] endobj 290 0 obj [ 292 0 R ] endobj 291 0 obj [ ] endobj 292 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 294 0 obj << /Type /Pattern /PatternType 2 /Shading 295 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 295 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 252.99512 72.2832 252.99512 86.45654 ] /Extend [ true true ] /Function 296 0 R >> endobj 296 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 297 0 R /Functions 298 0 R /Bounds 299 0 R >> endobj 297 0 obj [ 1 0 ] endobj 298 0 obj [ 300 0 R ] endobj 299 0 obj [ ] endobj 300 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 302 0 obj << /Type /Pattern /PatternType 2 /Shading 303 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 303 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 265.04199 86.45703 265.04199 72.28369 ] /Extend [ true true ] /Function 304 0 R >> endobj 304 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 305 0 R /Functions 306 0 R /Bounds 307 0 R >> endobj 305 0 obj [ 1 0 ] endobj 306 0 obj [ 308 0 R ] endobj 307 0 obj [ ] endobj 308 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 310 0 obj << /Type /Pattern /PatternType 2 /Shading 311 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 311 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 240.94629 86.45703 240.94629 72.28369 ] /Extend [ true true ] /Function 312 0 R >> endobj 312 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 313 0 R /Functions 314 0 R /Bounds 315 0 R >> endobj 313 0 obj [ 1 0 ] endobj 314 0 obj [ 316 0 R ] endobj 315 0 obj [ ] endobj 316 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 318 0 obj << /Type /Pattern /PatternType 2 /Shading 319 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 319 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 212.59961 86.45654 212.59961 72.2832 ] /Extend [ true true ] /Function 320 0 R >> endobj 320 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 321 0 R /Functions 322 0 R /Bounds 323 0 R >> endobj 321 0 obj [ 1 0 ] endobj 322 0 obj [ 324 0 R ] endobj 323 0 obj [ ] endobj 324 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 326 0 obj << /Type /Pattern /PatternType 2 /Shading 327 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 327 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 226.77148 72.28369 226.77148 86.45703 ] /Extend [ true true ] /Function 328 0 R >> endobj 328 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 329 0 R /Functions 330 0 R /Bounds 331 0 R >> endobj 329 0 obj [ 1 0 ] endobj 330 0 obj [ 332 0 R ] endobj 331 0 obj [ ] endobj 332 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 334 0 obj << /Type /Pattern /PatternType 2 /Shading 335 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 335 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 200.55127 72.28369 200.55127 86.45703 ] /Extend [ true true ] /Function 336 0 R >> endobj 336 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 337 0 R /Functions 338 0 R /Bounds 339 0 R >> endobj 337 0 obj [ 1 0 ] endobj 338 0 obj [ 340 0 R ] endobj 339 0 obj [ ] endobj 340 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 342 0 obj << /Type /Pattern /PatternType 2 /Shading 343 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 343 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 214.72559 80.78809 214.72559 94.96143 ] /Extend [ true true ] /Function 344 0 R >> endobj 344 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 345 0 R /Functions 346 0 R /Bounds 347 0 R >> endobj 345 0 obj [ 1 0 ] endobj 346 0 obj [ 348 0 R ] endobj 347 0 obj [ ] endobj 348 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 350 0 obj << /Type /Pattern /PatternType 2 /Shading 351 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 351 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 262.91406 80.7876 262.91406 94.96094 ] /Extend [ true true ] /Function 352 0 R >> endobj 352 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 353 0 R /Functions 354 0 R /Bounds 355 0 R >> endobj 353 0 obj [ 1 0 ] endobj 354 0 obj [ 356 0 R ] endobj 355 0 obj [ ] endobj 356 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.61569 0 0.97649 0 ] /C1 [ 0 0 0 0 ] >> endobj 358 0 obj << /Type /Pattern /PatternType 2 /Shading 359 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 359 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 352.91602 86.45703 352.91602 72.28369 ] /Extend [ true true ] /Function 360 0 R >> endobj 360 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 361 0 R /Functions 362 0 R /Bounds 363 0 R >> endobj 361 0 obj [ 1 0 ] endobj 362 0 obj [ 364 0 R ] endobj 363 0 obj [ ] endobj 364 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 366 0 obj << /Type /Pattern /PatternType 2 /Shading 367 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 367 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ 371.33984 86.45605 371.33984 72.28271 ] /Extend [ true true ] /Function 368 0 R >> endobj 368 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 369 0 R /Functions 370 0 R /Bounds 371 0 R >> endobj 369 0 obj [ 1 0 ] endobj 370 0 obj [ 372 0 R ] endobj 371 0 obj [ ] endobj 372 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0 ] /C1 [ 1 ] >> endobj 374 0 obj << /Type /Pattern /PatternType 2 /Shading 375 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 375 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 340.1582 72.28369 340.1582 86.45703 ] /Extend [ true true ] /Function 376 0 R >> endobj 376 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 377 0 R /Functions 378 0 R /Bounds 379 0 R >> endobj 377 0 obj [ 1 0 ] endobj 378 0 obj [ 380 0 R ] endobj 379 0 obj [ ] endobj 380 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 382 0 obj << /Type /Pattern /PatternType 2 /Shading 383 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 383 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 362.12891 72.2832 362.12891 86.45654 ] /Extend [ true true ] /Function 384 0 R >> endobj 384 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 385 0 R /Functions 386 0 R /Bounds 387 0 R >> endobj 385 0 obj [ 1 0 ] endobj 386 0 obj [ 388 0 R ] endobj 387 0 obj [ ] endobj 388 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 390 0 obj << /Type /Pattern /PatternType 2 /Shading 391 0 R /Matrix [ 1 0 0 1 0 0 ] >> endobj 391 0 obj << /ShadingType 2 /ColorSpace /DeviceCMYK /Coords [ 349.37207 80.7876 349.37207 94.96094 ] /Extend [ true true ] /Function 392 0 R >> endobj 392 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Encode 393 0 R /Functions 394 0 R /Bounds 395 0 R >> endobj 393 0 obj [ 1 0 ] endobj 394 0 obj [ 396 0 R ] endobj 395 0 obj [ ] endobj 396 0 obj << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [ 0.8667 0.96078 0.2 0.07059 ] /C1 [ 0 0 0 0 ] >> endobj 397 0 obj << /Type /Group /S /Transparency /I false /K false /CS /DeviceCMYK >> endobj 398 0 obj << /Height 36 /Width 128 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /FlateDecode /Length 399 0 R >> stream +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 +¯þœ$‹Ö ?.칚\o‚ÃÀ˜¾‘¯™x¤Ãz/§ȏS“ÃØRÀÒöþð`#ãm•74íøùý¶–‘ µPFŽ?Þõ¸íÖéœZ`ݚ÷d»£áôVŸƒ#iÞúÕ[äÜ5^àüö׿ÁÍ¥›mÇ[4ÀZwÈ9÷Eå'-änî®Ì¿ÿè0'ýWn–4'«Þ]¡.—©ëÖ@þ֐õ?-ÆNj>Ö>P˜Ÿ¶ækলVŽ¿_x˜ xûw.äˬì¬ëÁßwã&Œ·oÜ~2úÐrZÏ#Aè¿C@BÖÞåÐõû ÇÏ÷/0–Þg3vA{榼•‡¥°ñŸ×´Ê«nÆ·2tdé!îÌÒ Äª§@>–@cê)«Jäq²;düjQ›\À–¿›¹éûšdy» +zŒŒ©:;ÉNüø@Ð ¨9ªØ»2µìðõÞAŒŒiihëë8=|DöR\îjûϳ@Uêú&à>[Œí–‹ƒC²TužÑ üÅo›Þ҃ŽH¥RMqŠ4i²)«€Ôæk?éìþ(ZÙ´­P©àäîW¯X±"Eª$æÒôO a±ÆV@‚åX‚˜‡4*ms†JÀٲ탕ôÖ]kâÁHL]’E +‚ë·ûö)ö*òvúþ¬ Ø›w­3uW$*årMÈ|c"H£b÷ËUn®në<ÉA¶%a’M>›ƒwƒ Ãþ"Ýüg?Œ¿wYíõ®7˜M›6­]ãQ˜V üŽm;<=<Ôûã€ozVº¹ÀÁ¼=Ö&ì•À‡S¤N6Wy6>öôù_}ûCiyÀ3'› + + +t:F“Îøý½'<À৪¢2ž¼Ðj‰Q ®8Vßn8MjځGOïð0˜êÁ_èð@f–zŸÿˆIS>9¬þƒÏ?4|þmë…lr¶SÏoŒ¿ (“þSâ)»îkeÎ*óˆãÄúãÜRöZžXþ©¦SÃ×1:àTÜÄó€’olë–É×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Üþ+À¬WÐ endstream endobj 399 0 obj 2460 endobj 400 0 obj << /Filter /FlateDecode /Length 401 0 R >> stream +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}ã$c0éßÂÔ5>Փ*ë}°µ|8PÛfûzvµ­wÔ|Uxª›Œ‚Tý«K•¡HÎw¦ +!Šñ$Ÿã,#Ïðyóÿ)i>ûá4¯Ónîéa ©o©ößÀ[Í=]3„éóu¾§k“‹ø¨Ùä ™óŸìñðûî°ÝF~O ÏøŠÝÁ]”\`p?{ ÜÆ©K;qÇú¦0§WïÅ£ ¹.AŽ`ãހÌ.ÁÛï“ëï| +.Éã >ÝúÛ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*Q0.MçÔ@< ñëlaÁ¡qŠ +;ÙßQҚ6·Š +‡Õ³FLbm©zÙæe;³$4k#!<ˆ™(YH¬$‡N{†çē`3 Û`4e½“B·Qی™äz°`gQ—‹V< ñÎ8K·™4›§RH»Èu›Ê¬¨Y¡f4(Dâqªàˆj3±ë‚h¬uv´cD &8µ†œ| +:ó"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ôȁȜd +‹ƒ 9¬ëWÖs4àÍ:țµ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ŽZw‡=/) ê‚…¹×°ÙŒò]à Þj‹«×:Þf§l\¡V»9Œ”:l´7pMwP§ëoM +L¸NQ4L˜nLbƒlС@£Ç&Ýf>ß6@!xȜ# êô¶°`aÕóúR¤ÁA<Ԋ2 Q¨Ka.Ó]è !¨’…‰¦– rõÂ46·:g¯Öges=¯¾5ù³g³Ó²Ù,¿å¸çtGº.qïfo«M³¼,ë|QÖÛ*?88<>J,•`ùYŽÙ¾oþĒz™;îìj‘žÙ/n6«Òúý¼^^­Wպɉqèø,›]—ÿþ|ùw•ss=u/çå{ÿîèæÖÈ¡|vÞÜ|ޚ+Ëþf¸Õ>e‡s#0o?ÏY«£}Ñþ%ª@f0~˂pdb™¯²'‡O糓yvbœlÿߓa[?ªïÏpw·H 3ì0ÍX!À¢}êexgÜ\‚ËVÆýb¿¸» ‹{ãoÅ.îçÄ +bŒõM[µ£2sW÷Ëý⣙wÄÝ]Ózp¾½¾Vtâõ˜kY¼/Á‹?Ð85tln8;‘}ìÆFû/ˆ(ÈA»‚_ «$%ðîkfbàؑÉI;8;l tâñŽjÁiáÓðH ¡ºðM'ØðÿØ/|¿ãÞ»6´;”¹‚©ÖƒàfKè„ \bSÃ7W]Ñ®[£ÑöÏ=׬À’ªûgz†©Ys4U0|ÂHc‘&tNßènwI†ÿ×C›ß¯¯w•¿›®ZŽ6M;”ß‚^vzu +ê”ÖÉ0W¥àŠtÄύ6¿?<íÍÈîþªàˆ+Ñ_ÒœÚ tPf ê¤ÞÉ903RBãþöפkƒßFY°Ë•6Í"i!2œ±X»+Q¡Ý¢IHdëìÜ/-@£Ç¼´›é’Ôèça › èÜWÍn$5~‚¼CâÔqû¿A,, ¸°("+ÇhÉ&+f²«$ ÄÓS¶Ûå*Û=ãÉÄmôMW¹þë«°[ +äõìñ8òÖm€YžÁ˜ c½Ã4­|. +I 1JÝP¸ 黳·áŹz‘4d%”z¯²£ië€c–9–"»Yù²"æÅzA²ƒQº‡áèؑ õpŒ_æUGš‰%”­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”mi¨½ð“ ¨ Ñ©¢]°šQG*.ê‡=:OðÄ䯧ÁR@:FZÝ$aëè|ƕiæIKé«!qU‰h€€§îl‚1¤Â UáwÌâ±±YCô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‹> stream +H‰ÜV{|MWþöÙçÞÜM$¤‘œÜH„ j¨”ÐP´Ú (ŠD$‚„ $õ~Õ«f”JJ=2hŽRñ~F=Æ㊡¥¦Á`F¦Ó{Ï|÷D=iÿŸ³~;9{ïµ×ZßZß^çB¨†Iˆ}¯{ã&C“³¸r™£gBj|Z”ç šfτ1Zގy&À%0µKJœš™®en3ƒSÆ%}êuµ!àÁ¹oëäÄøA{j.[4ØO{͓¹àVdªËùϜ×MNÍÈüdÛòÉ@˜? ¥ŒHˆ/šµg~›>o¥Æg¦¹FZÞ"J¨¯ OMtxn"ç÷ËOi#Ò3|oõZÚ%c欏gϙ;oþ' .ZœóÇ%KÿôçeŸ._ñÙÊܼU«ó׬]÷ùú _lÜ´yKÁ_¶nÛþeáWE;vîÚýõ_÷|³wß·û‹ÆP Á0¤"ÙãID£ŽÑÈÀŒÅ÷ÈÄxŒC>B6Š‡‰¬ì$LÆMüˆ8R\Ä%vŽ«¸+þöc;¾D1¦`fâ[¬!3ÎçƒÍɍj•²C•nÒ]V—5dm*Ãe¤l!ÛÉöϱ¦¾Á›˜ßcNùÀòaå£ËǗg—O4Xä +·J™i6üzÑol$#žø}Ê°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ß¡ïÓ÷ñÇŠÞ¡Ì†© ¬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â¡Á數҂҉±÷U†(iÎÊ)vé*­²•|K¾-“äl¹Bäuª¿ÚšÒS¡Î¤äºtÿ¥ñ qÌs,vœÖCXÃt²ò&ª“UµÉ‹ˆ!Âþė@Î¥³+d’M™d0Ú)kÉà +œ›Ùö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\êð¼Ng¨iÏkFS3éÍè +Íè'šÂC‹BT£†Zû ­àxLV$ú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ºªJëò€ÌJiÇÒ¸­ «ÕiLSH£­”˜ØQ¢b+QA¡}‘¾AeÍڞH«ÓëMTÜ2 ö+H\©øÕ%¨E£p- +¯Šñ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ˆ"Ÿ,#ˆGi–É`ÇâøO°ƒÕtš¦‘Žcaú MS1-OVœÁ(ªÙyÔá_%Üwf넹p§ £æ0ôYÒÕÖx^£: µí‘DO<š`ÑC$1 NcÔ œÛX¨¨+§(ŠÍPÏhø´–Ó±¨DS¤Ö" (jËN»‡‹ê~;¡€‘YanFx0â²á\sm ¦A#M‡‚VÊí¸Zxõk|˜Êâ%¹[Y:×{ç|N ˆùHt¢ôK©ª°ØÏ8lfSc·Š\%yŒ3¬†A…´l‰Ûdd¹¸&/¿@`ù ®”lí:½ÖRŒ«Ølaé8.*´YYŠ°tív‡¬¤ë2Ô¦)}ܐ¡†O ”=î cgÁ8My„›²¹¬ð9éQ³=¼#밇³ö0!̓<+\ìaxøù¿Úš Y¬'ÍkÒAÒxš—Í"mšùãcw‹ï=÷°Ó£ïü}äàèŒ:ÌeGÂ0u7z_Ja_ +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¹¨Ø Poçì< 4ÏbÐ A«­iÙ) F3¯1ù4Fî-̛µoa?öûßxo½ëêë|Îñ!j€ðŠ÷Z?ÿW"#ï¬í~!£üì–t·­2S½'¾úgâì¹mLå5¿Zõ½Óß¹&¯~vϛkO€ánÔdá.JдQ*¦ð~8Ùsì~žÑaDÑ4ÇïgÀsüÈ¡_°ªðy˜@'èTÛ b¥6òÿè¤'RrT)¸ µIKiDÃåÒGÏâ­è”î}QxßT Ññ|ž&Ï´¿Qš¨yA¢$¾CÍD–0{äõâP]]=<"xÑÂq´*ÜçÖ 6ö¼¤ýõ~}a€±š W~/9yäÃ!fí¼6hv1¯À™”ì9 Ù86Î3W(‡1Ô 6Û3º°Ù¹0ª&òçÔ]šC^+†¶Žñ=¼Íø°+•Â‰TŠ õˆÜ&ˆyÐ8mðQM¬x3¿¹ø¨ã–ó/®/_ºt6ÙŒ +ÖÛhª‡d¦p@˜¢÷`ÔÏ*%%…æ|€I_+5šâsÔ{ùß߬X*fdIP/uB ƒÝ¡7ìùI¬w$IÈ@Äôz¡ÀÐ$O†·ŠÆº ¢†çF¬øðܝʮþ¶w¦½ôrêÚgXûàâÀ¦Ú;wða×ê}ã]/oØâ>Ž½gÞ9þÁšø¾H;ÙmLÅß{(€q\-*KU\­ø¬‚)*¶ˆCâP™Ìü\{\{4ïXÙ'š³KòJ¥zk™äªÐ¬•¦úWm Ñcéÿ¯Öà&®3zï¾´Ò®¬]I»«§%Ëz` ˖õ²±-/ƒmh +Ù,˜NpRl5¤”68©kHpSHR3¡¤ u’Ó2ÐL¦iJú2…ÐN7åK“¡í¤A¢wW¶I;ùýؕîÞÑìùÎùÎùîîê’cKbí1¼clm‰DcS”_f,;hÊ6Ûj)Hµ4®xt²ƒ&Ԃ¨¥Ð<d +ÿ*ŒsãeBë&Ik'sÃTO-¬K{«D'Î΀:‚Uxp¤ÄD;¨`<íÐë©Ô£…:'Z‘ᐔÒí •ö²¾vÐù`ÜC…D¸¦ˆÚ‘ê­ôéEвtÓɚZwyMylʯ’µî(º¿k¢å¨¶KDJÛdkz–ÅÚ˜eIß ê­Õ7Ð\c1OíÑxJ%K&Ì~5ø¸PÁSj8¨d¢)¨z&XÍ(Ò|2XŠIL§Ìø¹×¿³¿øÉs?‚®Óo^Ø\üpޞ+v¬;þ€KöKƒ+{Þ{â›óۇ<Î‡wØg´<_Ñ| .ûðÿW×ÿXØ°.~ë©ó?Þ÷ŸŽÛÙ6T<ø“ãÅ뻾‚¬Wñèܹ8 g! ôߙ ËA¤À¿eëúxôÓ-…"Iuí2]a[™יqNe:tá ‹L”Q­[)›T½zρŠ:INM÷ž8^|·x† xìÄý¯ýòê™Ý¯V6Á$ÄQ%ӉÎÿËcš«zʓèͳڛ»Ás²üŒpÇ†©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Î%l . +Aà0H"`«}-:ûŒÂ;²`«1 kql?×N<Š‘¯(—šl “ÑršS' ¾ÚÁY(½ÙÂ[0„ƒ¦0ŠÓ[`¡LŠápo/:ÿ$š´&b•J-'*ÿÀÓj²‹ÞK§î~ú©]ÍnLWøÿޞü¦…õF~ot}¹­Ïí¯«ln.®$RÄWÁ † rîYÛ³5‡l‡jN +§¢o%¯G¯%ÇcÿŒ1L¬‘ã¼GvT‘†<á.÷T3ƒ|uΡóPñ>Þ@äB¦p§ÞÐéÙ¯_[QaòÂmÞ½'¼g½„7Û÷u>dj»¥–ú½CSJ¾¥ñÞ¥žñn¡XÐØÏd&¸qUÒS¡Ð­óÍv’D«hqÕr}šyhŠÆ¨:s­È ^Q;úã + }„jlÕ%aDT¯÷N›Ed¦ÓqUçəÎp•Óåþ?ç÷&Ò,—J'Ó5åUi6ž'\U¦«Xfj¿ú¯h೤5y©ù]IiÊó©.ƒbÀx2Î[K«ÞíºT꒰m@~ia•G/°¡F_rýle_´¡áæÕ%ýkØáÀü–7w=¯-Þ1oç[»V®ñÖŸ9œ³Uëî;ßÜ` ú[¾ýõ#‡«Ò«¾}û…á˹•Íx].ßüّs‹q¤×“H¯>tp€r¥G¦ÅJš¤!ûhÃdàúYщ9Ö¢Ñ×ùàÐô3®Í1@=e¸ñ’&õÃXŒ2óÞÊ㔠çZ!ϊ­“š,W²Q:%æIIgÏ›"ï7ÎÇy]|Úw+ùi3æOŽ±ìœsë_XvŽ1‚”,Ùs)ZÞ~dãºø¦¶‚¹g±Ççéx¸0 áF³¿ˆp‘ )ë!¶…@‡>Š12¡Qgyê§p^œì9•ª°Œ6"jíÃcKŠ +ÖAîÝóñe´_AµÚˆœ-^‘=ÝúnCŸ¾ÏpÅy%ð¾óýÀGΏ̉ì~'˜Qøžl­´÷•ét +•»­v›¡yn•Ù!#lz„wRÅö½²xÔõ2Cg;ãÞæîq¿ã&ÝèÉë ðNlwÕǒÔ,@ÁÏÁo€Ìà®(¥i|ã-Å6^PÔۄ2R“åÕúÕ3/Ué-U:qhõ-U÷î7eØhÌtÍiU=‹ê•êaÖÇEóáá2ùFqÏÛ]ùÇÖ·dºnÿ·|©Öù´R˜‹ÝìHºŸ¼¿0Li +ÕɶË>Œ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 iIˆRR§M¶#R*>"àh#*R„©RRӖ޺ov߇¢JUÿíÍýv~ûvvö½73oÞ-=þ±JÕª@Fœ¯ô½iZ»Àʅ¬ÍÌ´t,ÍLÓ xgÒ ØÇd`˜Ãf§âŽP¯àN¥8,]%ó¬-ŒdxIA ž)!|p±c€ÚÆÀÀîCí흇tF2I§}ɤÑÛq³·çæ͞ޛ`Ý)£Í´.ƒä‘ÏÃU{5úŠr\;n?žu-KÌÒaƒc·Gð_^v†°'hgmºFÙqb«åYÍe4¢Ób†´ 2ÔhYkFï²[)“Í~‹²î6³{+ˆçŒÈQv- F{„#;Bs¸diØÊÕ &(cSZÏ¥\1˜u)/Ar󆏸ÙÂéôgº ÁïôÁŒð¹†Òrsó÷¤æ„;ÉÁ8”ér±”OÁä˜÷îª__Þ4óú?®²Ûçì¨Xsd[ÃìcÕF›xîB=Ù‚üø³C‹w©CU&ü}^ðî?ÝўZiݬY—®†Û÷žýÙºïæŒ\ö÷}“KûowžlhšY4*”?gÔ7çÆv°‡:g,*“ÌYZ:gîòôÏ¶ÏªÅtØ97„CçERÒö؜ê–t›4¢–Øüž;áivš»Á^,Sù4Ýւê•S+³†[+Ñû¬ì³2+·awüøL¯'Ããóð’( âeòî:Hñüp2ƒŸ™¬ç±4ÂLõ&™É]ê¨"Tw¼v¯nržñ%Mï£0‚ÞÀSñ²Í鈥%7pöŽ;w:’—£S½z ›Í€.°&[(‡˜½+\ gÈ,fˇÄÙ rküê×@én–¡gA‡Àý—a¡ÌZá̂™Cê+ÞTÔ¶b8‹Ý„Ånò¤Ø-°ØM‹Ý®K—ÐÆI)Û¦<í²aFÏ:»æÀ×c÷Ã3Ö¶­,Ýðw;ùÜÌù9yúª&ŽU9¬Ç°N"kÃEŸðTæékäéÙ+#G%‰(ÚÝ¢$ò·ÕM 'ˆ<•`¥Š§¹Æ“R*셕=¶Ð­tí¤ÓßQVØ|×%“I¶me–É"LIYtŽSœC@¨YÉ¥“(ü9b”Ÿ§1ÚúK£D(ÿç~ÅÃ3sŠm ™Fþþ‰øSJ§Ê+巅·lçøób¯v¿.\¯)W´4…ª¼*h¢ªˆ¯ˆb»${!›´qª¦µê…þdEàù­œê¥ˆUu+¯xyÅqœJÙ&Ž%ž“UãÁ@`œ¦r +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¿ûŠ„‹)Ð~¨JWQîù@-ðÀ@%#±~Ðøp pǺ§¨ßœtöb_W¬>†Àú¬ر€m¨Óg~û ð6ààÛ(/ÜÂú4öì0œ  àûâ÷Nà÷™í‡¡Þ‰2Æ¿øö¿ð>>c÷WGPœB›™ïºQÿ.ÔɎm—à7À?\ÐòŸù};bڜ²i?¶£~Ãqâ1¤t{]¡}Â|ûp Çh à#ì«ý,xä7ò úzøx– ?~(Ìdúlì&"VZöšs/Õç-äÌGm€V㳔¯Êq '¢?Øý _›cô»ŽïÀ³oY0Çu3Úߏï<ö«Ãê«Oðoù0ìxF³ ¬š“ þðgÀxÀ[–Üœ«Ý8Ö¢®KЧ,;èR@`špqpu‹s¨–ñ ,­¤•rfùí¿•{ô7‘;úX¹ÎçByÊ¡ç-ân(wÅ»Òjé’|V™€¥VéW—ª}¶f(}Úríûûvûö´àÿËS̐}ŸÌ'2yŽÀîFœÄC¦B¬ÿB>wìi17w(Öo¶øÈvä¼½9OƐÎÔF@rÈ9ä°’ÛÈ%8+ÝG.“éT±8\ì´9%*­FÎ_Šœ¾¹<†\G.ߌ\™VT@O;ý>rÒ=&—™ÕôCäL~Áä콯èߐSRÈÝEÎïGΓE|>r”ñ‹ÄÏ¿‡\"cùTŸ2iåLncú«Ãwag\cúo"gò“&·›ío gò;&w2}Dr‹£Lî¹[\ˆœÉ_4¹—õ#¶#gò&iÊû3ùEÆŸ)7ƒ\²1n7õ—¦ 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æ¶Ò\uSº9+®ó–_]'V6\çÚëµÙHãëÉ©Gåÿb¿ÿëx¼È+ ôµß·T'1ž¨q¬ô~O¦w¹DÕE*¦Qm33NÕS&²¨‚šEŽŒ`LJq#²1s¨RÈî\̃9¸‰îð|”ª¥›‘K™&î›Snûe¸Û)Ö>a滥TƒŸÂ2ÂEY¨Ë)'zP‚OS ”¡^:ågéÔ+)kÞIQ\A§[Cñ»•P±÷`=6`#6¡ŠÎ¿™ü!b¬ÎÌb ØBq&âH¼Zp¯ómäµòÍvòÒ}؁.܏Ø…Ý؃ÏQÝÂ^ìC7Dö£ŸÇC8€/ Œ/âaôá ¾„C8ŒG˜…ª´¯à«8Š¯áQª ¿Žoà›øŽã1ôSÕöð$¾ïP†ø4D #Šïã8§` †A<“TӝÆ a?ďð žÅñü”êËçð3ü¿Àó8‹p¿Ä¯ð"Fp/á×ø ~‹ßá÷¸€Q\Äð2þ„Kø ^Åkø+^Çeü oâïT¾…·ñՋ§l ›mµ¾º¤ŠS5_Րäö5¶U[=í­”`][}gK½¯ùDÆ {¦&S1ø†hS¦rv`”¸frø3ü-~ ü-aÿ1¿æO2xcTÊWŽ,_ORŽÇXþؐž$¿d° nQ‰Ú,r›Áô¨7ɤ:Ñ¢ ¶=ÿïytäq\ûÝ?EÐèL«ì7X$Z§®éÄ ¤„îhV¦ä:ÅKñGv™îæ#ì²>)E9<È不¯ðîÝÃw˜ô¾íâ»%5üÑ·Xˆm¢;(³7ˆ³Ð]•Ùë,¤?6[Ž±×ˆÙ#ó +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ÆÍâ EeË 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óCüËxÚEjœå1Jí jcÔ¬8Ãûh¶rk:õ +µ[©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ÒUgÕÜköq<ó®ê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|û?ÂÄ endstream endobj 403 0 obj 11087 endobj 404 0 obj << /Filter /FlateDecode /Length 405 0 R /Length1 16291 >> stream +H‰ÜVyxgÿ÷ý¾ïÞ"Bˆ%Ò¸."¤HŒ¨eT,¥è¤‰$b‹DDšµTj«¥¤‰RBì¤Z”R[(b‰”(b»b¤„AÓ¤ó¸ùæÜ/™¢&Óÿç=ÏûÜûngýs>'L‡D`¿-[EΝ¼€w®ñÝÙ¥À P+@s ‹µäE/: ˜ºózÐÈèˆÈEïÎeË¥aĸ„‘ǝÚÕ÷®;F…‡„iühÉüڎ⍪£•0^¯åu£Q‘±ñ ¡YƒyÈìqQ¡!±a“cï¥,³02$>ZÞ_Íù¾e|Hdø¶€Ú~¼^˜Õ訉±Eþ¶mV¢otLxô€lf‡vXÇÝ ¥2-† +³’£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œ¡”þˆ€ÿŒ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Ÿ»·4PhLZ s­Ï4ZÉ-»ÇºÁ˜ÿ}$³ÞKØ£9lõxök2†òÁ>:Œ\ªƒÍ4…ÆÖ&ói¨£òÍbìÇßÙ!“7qˆ_Œæ÷Œ½8LçDüøv¸¨ ~9„O“±§(.¼?…åy"J颧ê·Q-91Ì'&ÎÄÆF„“†l™¨û º°$‡nÇpÅÊ]ö²‰%Eqä¾àH®aTàW“¹+~úy> åX&rôæòéN~wŒqa3¨˜*‘¦:%jˆ@1CÆÈBÅMùY=¡eë…\ ꢏa«C§Dæ° ûøým~åE­i Ý¢TY­¹pÎf: 3k]›kXoôgÙàø:YÈú~ŽåìÇ£ø•êQ…R$G¦©*ƈ¥"Uv’Ãån¥‹¤ÓÛë—ùË .Á >茾§† œ#Ÿ2·T΁ öøvdÁe¶º”ÌT™"i>¥R¡ˆ³Å¹•¹W{©©j‘¦iËLK”.Õ]tO}º¾GÏ1tÖ _7bo{2yÁ8f~ðgê†Ã–Pƒ¢Ù—Ž¬Hä¸%³EX‡ç½8€C¬‡#sØ¿g9C.•{¹€s÷9SMªC¯³ß¼©%WÖԕúr]DCiW÷Šc½WQ¥ÓÚoкGEkÑNŒ‰b†X"²Åmé*Ýe}9O¦È•ò¨¼*Èߔ*L”J‚2KÙ«dó—›3“Ÿ:GÍPp:¼¯EhL÷í³¹~Þ´—î(½QúO=Lߢ_`(ìÑX‹É1æËԉ©;Ùò r‹ËlNfœ­0–Ζ;2þ€,ÃÞÛ\îpýzÂØr&'Í©#Ûúõ£w96Q4‰;Ê,šgDi9­¤µ´Ž6Ñ.ÚÍgÒq:AÙt–r™.t‘.S=¤_è =¥gÂITՄ #¶¾h"š±w:ˆ¢—.¢ÙGILi"]l›ÄAqžñyŸKQU£k¹J ÷­–²-ã-€©§$㙦ÊYò€¼&Ÿ²”:Š7cæm¥·Ò_Å4‰½º˜)WuW}Õ¡êHu¢š¨®U÷«Yê ¦êSÍEóÐÚkoi3µõÚz“«©)ÈoJ2-çVÅUøøË¥F Ç0îï5أǴ…\QmΒb¤Q[å¤#öŒ¨…äN­µ!j;ÞÏá;Þð¥Ê꣬–¹Z{Ψ"Æ|’ÅÔãn]­iª8ÈölÀ +Öú&’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ši ;ûÞ9a “–øÏ÷ÞûÎ÷}ßï÷ýî½#x+zÛù[ܓè³½Œ»Ñìe7âËȋ]¦²OB§3¤†8Ñ]æ +ën+cvÃçŸäZ‰ ½mºÈÿ•Tã_âs‘cì v'`3F梻¦2Áþÿ”<Æ>Š¯Ú½Â¬‡îN3±  ÌÏÈ.溏'߄¾´… ¢óêëjkÔyÕUD•JEHž;§¼,XZðû¼’èq .§Ãn³ZÌ)Ë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ÛÒCiFGk‡B'JWj§'¯£• 9³.©„ôÖ ’ꍗK(³vèd‡&wÌ^©­ÜÅŽ;]S†Ý1Óx°fX†;µ êé"b‘Òàër¿ ‘$„[èÇ@ Êô·€ü¥0Ôj+T&Ó›Â‚"gî €XÉݚ=Ó;5Ņ;ˆš”ÈëÓ¶®ªú¼y”|€‘-5Æ kkvéÊvAÖ;¡dhu.J-®‡’‡B¿±¬†ú` ¬IÇ2ê ž@Z½šÒIš®œ›^ñ~®ŒL¯<¸<­QO=¯nŽ:wÌd“þZ³–í0wT ÿ3oTõ+ÂíU¹ U€øòj+„ˆZsnÏ¢úžÜ¨©NÝ'œW÷\‡¾Qanïb檛߀{PO¨YäRY( P£Ïe¢<#F"Jϯä‹6"f³¸|¸ðÉ };ñ%l-KÅ><HºÒTÿMíµË‡ê×c¥\-üktn'õvûýp?y»DÏÐ…{—µáFºº0ùv;Ôď¾úî›ýØaïGVÜýŽë°™÷߀§Õ×Oy‰Íÿs<pb‘|ëÚ»ž¦ŽÀ[ù@1Ñ|îöäw;ךËçñ"šGO˜›ÎÁÏ+Í~1øfæìÕϾ¯ÃþÉ1º»nж¾ÿð߇Ÿ|ýä»/ÀÕøWó'žZPx9·ÿðŽ)Ôàè!¡ +ô#-85@Ìáõ9ñú,²¸Îà/‘‰€œÍíñt!'ç4€“›½Øë5ٜ®nw•€€Ÿè¡èɀs»rÀNìîÌÆ.G¡ƒ¯V5†ò±YøáÀ„0¡zü€^,ðÍox:¿¨4‡°ÞlìVì+|:…]ËÐc‡$œ*\üšÓÏ̄ïZá^¾Îôßk1 ÏÀø-a×#/bú´Ò;fì+¼²Ý×ír‘²A ï½a1N¹Y<|JÁ˜£¹½Þ’oÙ_7ù\`8÷­?1Ì76 +ù\L0p|¼çq€²ÍaÈ^Ñ0kdÖÜm„'72ñ4Ÿp¸©ráŠ.[Qø|âÈ©ÜGê}¶ðRá>°-‹™|ÏuÌ.]ºïÒO?üä8°kÃ¥BÞU=|øÂeÏÙo?ªîžfGÈ:£¹ýþœÎì^ŸÏ!ùћ%ŸŸ´:þh?ƒë‘Ø÷½“’…Ú¥Ö"]ˆ´Y[‘»_h¥Â}䇌W’X¶Šá@’*V@€õoß)±ãK¬ïa VAKgUÝ#üƒ6cLεÞjL€ò„þoà +FÇ}ó€>/ÄbôMÌþ"Vš£ZƒñPŽçC|Çâ!éÐÆg#ªîõ—„’{+x¡Tø¨+,B"òy$YN ¢ú‹å¨>ŠŒ,üž©¢lpðˆµ5Àð<È\'ô4Fk®‰2Ês‡¿¥&0Îål.:ÇS¨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î®Åúð 9dÆzðš;/í7·ÝÞò6½Ã°³01:Š -É ”ÃãuëŒÛëá‘¡ÉÌ¢ÝCiˆãDw8&ó”vï§nÉ×£Ê:Øuim÷uJ jT*e°øގ1‚›ÓƒmÕY[|c=¦¾ãrõeJ¶Õ¯²¬ÒáqÐ(Ú`ãD{ÀŸ=øQï܍8fd6˜w¾ýô÷ö5,³Yml‰ùWË¥ÓæŸ`áÅuæ©ÜPÐ\ï(3ÿ‰ oØ4{¶9DÑ÷AŽÉA4T‚žKúêI3!:q#ÝÞ&t€®»)ô!l‘µITC Õq¤J·ÊÇ°{„ FxDúáq|M0ç A%[m³VÀMÛÍöbXW&¸0šiX”Zt†Â.Øë1U…­Îyß͓É]+­ |Ár9ÍRp¸ãv6£Ò]p–Dâ֗Z–"¬ERø|í³êgZÌZÞY@¨èæ E,…üTÇß1/äG _¯M¿Ù Hž½ÝqªWŽž<«ÄSý¢ªc=ƒ;úý4gðSIN‚:Ø e2äÒKýð† gÈÍ£BoI†üùh¨×y‚\Dri A%zòÈq¼ lq†0•£ášŽ]®“.K³§ÇÁilšQ_YQ2¿1qE^¿ô6©g«6ùÖ®nzq÷ÀÇ_>Liü­Ð̲%¡ö2FBX(ö{3ËÐ#ªè3lïu:aL¸ò°T1wn‹ÞëϐO’j8©~>)âT"ðÞ¤aã$¹†’(L®Ân%B a#lŒeö@fÈÍ$~!Ãår­Êd†„ú&ïÎYÀ³£ºfa×FCّ Õ´x<¤eC bþœ•ÍŸ¡D“áhjÕ ¤@xèêDlà JN\÷4êø¤VfQ•]Â;ðyéõ”çîëdõ™dúyý­ýMŸj‹~iîM‹s7…e ¾C“¸@G™!Yϼ¤¤ðÍcÍÕëö@R9Š$þ’5’QY¤‘yå#;,!­kÍÕyq-Wg€` d& 'ª2ÿe¿Z££ª®ðwîcfò0$&D®±…òÀ„IQy"#¡yH§ÉF“ ̃W‹ï6<ÄZ±¢¢UT” %­奅"(UH‹°ŠUcqñf­®&éwÎ\`¨°€þêÎ]ûîÏ>ûì³÷¹{êfGªÙ:rþårõ«Kà{³¼fú©ŽÍüLÈ»¶ @z1¹ ÿœûœÉòÆq:äå;Ã1õóªMåÉÇ×õÞØmSòœõÙM£S¾ù¬ìòÎ×'LX¸nœX^Ñêé¾hZwƍc^}¼[ëubïˆðE=¤ÏJX6È êˆýsÜaÄÅÇëpêéIñ©iiêñ)ºOèŸãHk§·C‚žžfħÆ%NíŸòŽx†èÝhÅÅ¥w’9£Â$ýHKz‡#Inw{w’Ì•3nµmY|FþgºëŒk6÷“…]r>÷¨ò(µ}Ú¹DҙH%ÿ-̒©”4l¢ö·]w|}Ãw­ÝyZÃ7ÞO,®hl½i•¸öŸÏÝ58?wV랖7Ö¿.w%ióÜö§õ‰‰ýN¡“ ò÷êö…‰’7ÎxlË¡Öñ®mÎj~)c”¼šå\×Z +¸*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žâ>fpEr?”k¤†õڋÄkI_iØ"1+“-z!V-˜mނfméhÛ4c ù|4;7r=öÓÎ)ÿ¼q ú 4ëoc¶#ڙ· +ÓX†]ŽÅ”„q‰q \÷5́ûµ•ÊyïÉ}+Z£tÛ“²?bÓ÷HÚ(í‹&e_„H~Ö¶ÿ$i×”(j´[m£¨»…ô¾™¨…Ù»ðùrÇa8ûƒ~ú»±R› ÏÎY‰4Ú9ٱ߰í;O"“k¿D>”1ä—gç¨Áq¶ÃzµhOêèh@Øٗû¨‡_? co:ç§èKññ³æ!žåxì'-aÌï—qzÖWÒ?®<ƒRî hâx“~P¼$I«OK’ñFÚ£üγÇʖCæ5˜ªíÀ~Ò³”ñWªb±«Ý+ãR?ˆ® +ƽŒ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û§[Ö0u7h×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îc2„Ә¾aTU E|Ï –~Yéh‰lçÀÍ'‡h ûªàE5ü˜Îviq5jØd«µÔ˜ETJm!…ry¸‘‰>yªggT“Tú&S.¨ZR‡—kOã»RIž_?½¢,…™˜B) ejý0uøˆj©!ó;®t­‹ù¢˜ÒÎöqä⾊–(TÊ G +£¼vùyåʎ m¿œ™…|΍ž™×ë’­â<ŸÚ™\%@UªRºå-âG+Ùª!0*,®8éªÎuPTlx.±· e¢£ÉÏwˆíËÇÝYÉóûÊ–÷²ÞŒH âüóš.ee´Ìy›½ôÈåö%e®.®NZîç}ʗ²ÿB™«?—ÿγ¬ÚNnB[_Ê]ìw‚ŸèÈÇßà‡ÏÁœ‹ŸÝX´ãG? Hä? íYZ¤àZ¤" ÐéèÄb¡3®G®yºâFtCwÆáMø~ˆèɬîÅøëÍ{ ‡7JúàffIº[ +ñ#ôÃ-¸·¡?3~ ÷:C¸‹¡ŒãbÜNï–àŒÀ¼—îbö—ñù13o4ÆànŒÅ8ŒÇ܃‰ø ÷ûSz¢R²¼1|¸—¹"s¡–žžBOTf…éÇéôÒLÌÂÏðsÌfôÿø>„‡ñÅ/ðKÔ±š‹y˜Ç°€åЯð~'±Oá7xšEÌ3,±ŸÃb<ð[2/a ^Æ+xK± ¯±œYÁÒé –2õÌò7ñ~‡ÕhÀïñ¼F¬ÁZ¼Ã‚j œ Ø(\"ïãØŒ-ø‹«m؎°b'vá#|ŒÝøŸb>c¹µû°Mø þŠøS‡ñ%¾Æ7hÆ·øŽà;Å1gqx§pZÄb½3«ÖªôNr†k}ÙÙo–<7'7_ñÜìüHÿ€lcH8à—¢ìì›çڃƒO'LmÚh±AdŠ­ ­.’·Y­¥[#v¯Öº(-uƒHK]$×| ÝR—m\Óvº!†|]ÛiFXªè£9W‹I]+Aÿ¢÷èòÑâwE&Ã'U¤4ìÛâL]#RúÇþ›ò²‹¢Šâø=—væöƒî¶.ׁÒN—?¨ˆ"C·Û]º~€Å:Mm+Ð. +."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ówE‰CŽ°”3˜Ú›jÑ7 ìIw%ïɋ|WïÄa?sñž_çy´îÉ:¼Å¿\+€ë~͵¡ùÕµZõs0M,8Ž§™Ã•œõs#b?¸™ԇ]k>š!×ÒÑ úfHò`·kЛ+`±êæÈÅÞI¬’ïp»sØ^,³ÍÍô"ºÕ¯ÖçfŽé“°…ô‹{„‡ý:¹V/Îa &ßK¦0y±L.ÀBbЉ¨¦ÿ)òOV­~«|ƒS¸€y_Y†Zùç¦åñD^=Ä÷4iM5É6#OB×/F‹7Ã-W‚Â(%¢-‘ý -õ_WW?¶&~‡¦ëÖ®ÙxûGûw’k÷5—ὄq!u:_´8€ŠR²M(¶Tl©ØEE«sÞÆi;֙Î2ѹ^g&WÛ:Œ<¡Ã‰xžîÆD†Ž$ž•ñ™¸yã=‚I #°«¤]`¿z+fÒýK ƒØ,NL‰™³¸À> stream +H‰t• P”GÇߛoºé#D¾ï›aÄÛ¨b<ñ6!F Æ3*È©€€‚âGðD%ZºV*‡…qk׸ÙcÖÍF£I­ÁDãÁ„A`ĸñ*:Àl3¢)+kõ¾×ïß]}üªû5 ´Õ @ÄÄȾý<©·T®I›“¶ovC>öðÛ³8CÛ2û«U¯°Ðø´„Ÿf[@ /€WqBòÒøÏSo¬èfh«%ÆEǖ‡–3´„&JÁûø3düªŒƒS2²R"Á&㙽º%§ÆDÉCírdÜ+%:+Í·¯é8ÀŽ²¿¶ :%®«ïþOd\$×36-uQÆÝáË~Øß`çÁ´…qi,ìoá€Ã +D) ±¯a0àìC6@Ž°ã‰7ôƒXEƒ—™Á`0ÚÁóvìqAkÙu¼œÀÕÄG7ς}GñK ðøDt7¥)-;wì›+½‡ÜñNéÚï£o¼¤æ-›=À[vñ”ŒWB•×”©Jª’©,SV*¹ÊeR¨Ø§q½±Ðxßøˆù°=ìßì»Éê½yÏ L +ú(è‘ÚA TǨÔ©êtu¦:KÍV¿VO¨%êUõ¶Z¯6k“µ©Z”–®åiµ +íºŽº¢sôŽznÖCôžz½¿>P¬ÖÃõ}‡¾Kÿ@߯Òÿ©×èý¦Ù`ö0·3û™ÌAæžæñæ(sœE±0‹¿%ÄÒÓ2ԒmÙhÙl) +¶ÿhÝcý»õkëaë·Ö£ÖãÖ¬'­g­EÖÚïv!Óºßo06D7¬jp9NÕÙÛêâæáí 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³%Q2¥ÐJ D&¥Q:-©"M¤Ó"Ê Lñ- Å"‘AKÄ.Ê¢¥´Œ4Z.‹%´‚²i%­¢Õ´†Þ£ZKëh=m ´‰ri3)¢‘ŒÄ„S<‰“‡g†g&†ñ^Ⲹ"šD3!„‹þÇq]xwy&Qþæý’ygnp§¥‚»KK·-îî.õv¥ÛÝ®/îEqCãB< àIp-ÜuÝsæÜsæ/¸Ïuœ“Îy¹îÉ ¹)·ä¶”È-¥¥åw¹&å‘<”'N±sÊ9í1V¡sV«kU…úhY­¨•´ŠVÖ +ZM«j9-/Ë1s0ó0 °‹°K°¿a–cV—&ÑdšBSišœ¦é4ƒ~¥™4‹fÓ9+縩œçfܜ[pK¹ åöbö{§â á0ŽàŽâÍåVܚæÑ|Z —q'ä*·á¶r…ÛñG8‰B¡§h!-’»8‹s8 ¸ˆK¸Ìs{9ßà +®Ê=\Ã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•~§ktnÐ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úË(ƒ$Vâ´­¶“Á2D†Ê0.#d¤Œ’Ñ2ƌ0#Í(3ڌ1cÍ83^ۛ ÞÛe¢™b¦r'}¢Oõ™>×úR_y\õFßÂÁÀ…¼Á°(”Bi”AY”CyT@ETBeTAUTCu¼ƒwQïá}|€QµPuPõP ЍÐMÐÍÐ-ЭÐmÐíð>F{|‚?áS|†ÏÑÑ Ñ]Ñ ÝÑ=Ñ ½Ñ}Ñý11ƒ1C1 Ã1#1 +£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ÿã¾êƒ£ª®øï¼· ,¸ùñ-υÀ&DD$BÔàîà +&!è.~íÆÅ ©Eþ°3VÛí´¢/ю§t¦Åѱ-ÊxSÝX§Æ¯–Î@¬H[¿¨µÓuŠÖVû!Ûß}û6 !Š3Nÿñî¾ûî»÷ÜûÎ=çw~ïó~æ,æCæ£æ!s?zOCæÍ=æƒæÏ̂ž©»@*1 ALÆLEÓ0§!Œ*T£µ˜™˜…:ÌÆX˜‹Ó1gÀFóáàLD± QEXŒЈ%hÂYXŠ³± ç`9ÎÅ +4ã<¬Ä*´à|\€ ъոq$DÖàb´c-R¸ë°—¢èÂtc#.ÃåH#ƒM¸Wâ*\kEŽúëÜ&ϼæ:f0½ÌWú˜±\Ϝe+ó•mÌXt¾r#3¯ÜÄ\åff+_a¾ò5æ*:Sù³(c݊¸9ËkwàΠ͜é.|‡9ÖNÜÍ ìžà|zf¾\À,ìûøs¢{™e݇ûƒõx ¸?bn¶Ò¦{™u=¬s +^tèH)`Oà§xÒ¼[ã>p‘€ H6K°Ä 2‘Žã?ø/¶Éäz™Ž·K•TË |Yf–9Af˜'Ȭñœ uš·ILd¡F‚ÔËR~/çʊÀ;åY.ȅҢ‘ çKë$¬–v<,k%9ž¤MRr‰\ª9A:d¬Ç'üA:¥ ¿Ëß&'¼‰?ÊUšäjÉáé‘k%/›å:ü•³å‹8*[å ¼^£WP±~˜ ÖêQ øªWÿÉ«_Ž©øg r[ûOÁ§,Aÿ +|օ¥—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­„¨â©á›*ûq^Šð÷˳Ž¿ëÝãïß8Š¿áWÆc2@͞'ë¨íéÒ³ë'hmŠÑwY=2\™±Ql/Œ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­›3lhH0™_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§Ý ¤Z¼[©o}°°&V#‘o{‡zq/d©AâÜáöb„;ÐÛváÎÏ áGïáÇîᕷFxú¼R"Üuk„ÓºÛ`\ ©‡Ô¾¤«o‚ô{·‡tM1¤£{k¶[†­ò9êí¥¾‹/që(¡Dx}j5̉bZü|bÂf©‰”B®›§ð©(Žy +·˜*1HÚ^^®Ã°þ‘N+((ºÁ…f¨œ2ºÃà!Ãà êN!Ü,êŠHۚ*gŒ©Œo`)v…1¹·0,c‘1®ŠQ­>K&q z'QYñ¾“H¼í$Hy²±1m Ϋ¯Ì&w+V|÷¶ã»ËóÍqù›ÓŽÓÛ1ˆ±+‹±(µa.µÝãûs/í?§Ï+Ãךàw3m¡ß–Û#†7‰±”0¹›¼no^]C3~?ƒþЂKP-¡mÔX‰@_YˆÝƒ®Ze‘æ&øԙêl•é\½;í³ø҉†HšÃi`5°«EéT¨¢û"þʪJê׫tjTîÒ#}Äöëàcºí/k®Ö{hYGãqˆw´;•åÖÃY+Ûî8VÆq²F’Ì&³qâݘ‡ñáCNœ8Ž÷i Ä‘°¥ð@HÔ@cwuuq:Cð鹋îËð“ñÝÜ¢/nýÙý Þ8±o¾2üÚ«îÇ=]î;?ÿ-ëLu¯^týOKº{÷ ÎäßÐñ,¢±Ón]Á@e°T]ÔçŸé§%åÁhs„C…€®Rè +Âõ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àÒ³.å¹\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ô ß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»Á?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=öµñ~mÐ'ö «à“SÉuL\ Ý#ŒEGà cˆ‘oŦÐþ¦m21ı|lÓo +¡óã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\}ÈÉJíßлTc÷5¹-¼Çô66|§íÇŸ‘Çí7ýc¼ÇYżÑxáÀ ¬o¾sqðGºÀn°7ÂUØ28Ž¤ƒ3žúÿ€=ßvºPOý  +¬3Lyg ›Þ oP?AÜÕ7h­ÍÀtâߛA,Sný4ˆ…æßõAlkÀ¿AŒ»€M¶m¥¼>DýiñÄvr͋ʹU›øÉ9ZÈ(|mM~•Íûµ‹²»ÔðòŒq5Îÿ¬²¾9â‰ÇGÐÃjYdþáñӍùŠkÉ(§*og>v«ybºÎKýEaNšß)'%rю<´‹TçÐ{˜~'£rÎæ/ï§É3·ÂíàN¹ÍþYû?í•í§“Sé3Ÿ>Güæ„GùaÀ½Õ\MçqûÍî¯iÃO0‡æy¦lÞ|Qy‹¾ä`f^™ó(ù_ìóUH/+¬Å´ùÏüǖtkHOÃ&§Vö>#§÷I½1}ߺþîtÒIݕяÑúQä_C~ úÙí7w*ßNn›OùáÎe÷wäZ}à)ËœsÊÏú;—Óω·jG9yˆ<ÿ4å +æý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·€eœwkø¯ÄBÖ>¥ñ92Oaõ”åÌ3Í. tdWò&Šö͛†x×p ×cKÌ}Ðzy6¾âwÃÏÑ1 :ËH?ÙýŸAÿŸãgŠˆ ùÑÈä0Ç Ù?’Á¼»¹§;@G÷¤[Îr^öß½Eœ–¥óڈ£œ.wäsyhÈ7^©]9+y«n#.…썔^n3~: ;)Þ·È=½›†È÷©Ï»„Ñ•®SÇj?«s•'/÷0÷`…T$6HyÎm2:q”'’Øêq¼ˆcOà~˜îãÁXw/ãö2îôÿ„qÏÁÏq§KdîåA¿R‘ü y7ÈèäRž¬•qÌã$îGí%€ßšÀ_ÁQÚQد±–פü2ÚU;‹ÜA¬õcqŒývÄ2ä.“Šœ{7r¿‡Ü—ñ ÇdÉå@îZ‘Ü@ÿ×ÑÉÐÉdüïex<ƒœșÀ¸÷SÂþ6³¿/X_¨÷H¯‘^¢}GëíΛí…¡ÜRäށÜ-È= ã’=û!r³œK²…q–Tä—òÜS2:·öì—{PUÇÇ¿÷œÝ=÷‚>*ⓙ‚oQ”ÔhTŒ8F|]M¢´âûIÍD­N…Ô6f[í¨Ñjœ1jbªbœÚZ°ñ9&ÚØjm̳ŒÑ1uÔPÝ~÷ÜsÑ:vÒ?;Ã̇ݻg¿Ýýíîï÷[–ó0"f$$×ñ­ëŽìâÊ7hKùN”ïMùՔßrݏ„þÐ Ré{N1é3Ç{¾Ó¼AÒHËGÍÛi©žÖ:ȬÊyU_cn]!¸Ç‚v°>5çI¿GŽÏí¬Owݘ²M.Ž#ޮҚ2)3ÂÈ<|dCÆbRÿ{,š‚R×:;¯ªïÎ/ÂÄGž‹Ä;c`ç 䌃Íû̜<cîÿ1s y¦ÝæXpQÎÁESâ[þä3/eÌ11FFâO´Î±šò¼•©Rôå½)£ÿß®"Ý=s»¼¹åê&ÒÏWˆèw ™[´á7ú­`*’b†!©Á4$9ûÙFŸäE:ïnºñï&f™xd|ºVR~¾—§Ÿ×©ÁosŒo‚›l£?s&b@(ŸùéÏxgb™‰ԕ/+іï¥B5•â<Î ë ‰–ÖÂ20ºXËA,ïõ„mðPOVяùJêráÝÚO¶Sﲚõ*r^^ՇåUÚÊëG™³f\»Y Cu d}¡ÎXÍ{¶Zöå»î÷Šk¯‡b†y󙸫r#±—y_ó×t5œqà?ä=Ñïԕ%âgd\}ÍOöÐçÑ·/F_ñ&ãÁÃåK|ÿíÅ'ƒ2;9n4‡ûÕSœ{6F3V”›½wÇgÞGF›} ýË Œ“QLLböqe°Wî /ÉtKPîž3·ÔÅ8¬b±HÜóâò=ìw0Žó§¦¢Ä¹ååe÷ã×XÓÏìµ#¹Æx†íáhl3qۍÝÝõ»wD”r_BØcr×.(?ÂÔzóìŠS\Ï~ŸCyÆÎË©j3 +å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ä]# [M¦Ž_ëöôɛìz(çó{s‡œ3|¯V3¾Ã.'£›yÿÈmº–¶©ÅêuúéOÑIL×·ÄWÜëWêsÝñ<ÃSܸߚ:[ƒ÷äÞr²ŒvJ2¶’}Ð@,F#g;–¨¼,Wë/D7]éœD?µÏ©uP²JßîÃP§ýÅ ì*ÎãcýµšÀ;}à ×çÅ5}Ê+Gï¼óOÌPK0+:^ý¸ëè{R0LîԗÄ!}MÞ@Oúßï«jê>‡>¢ å×c²{öÿ„7éS‡¨æȦ?ÌòæÐBF>©¡³¿R+°Š:‡RgosÎÄ÷´æzÛËß0ǹÑÍ{Ÿâq‚ôöù]û[1äA$˜Ã3‘ƒq„gá`o`I_ib‹õ3}Bôçý^ +ÈçÙ¾‘Ü`ß ¤-òD¶>Ër„³‹ö/Æ+â2×BèJ͚=q¼%ôA…¼ÛsL)Ò9×YH°+‘GúÛ?Dy -í,Êqnâ-ŽitlEÏfœ A>†8{6ŠìiŒÁy,Dà\3E&Š'Qd ҵϲ½Û¯“4EŒ}·°ÀóÛ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ÿv!ŽO»'P¦Ñϙ&ûøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøü_bc0ñ‡¿ÐsQ{¶ùŠ±¢ÿ`:¢ò½z'¤a¥WïŒ.8àÕ»`.{õ®èHóêièxÖ«wCZ Ä«wg½Ü«§³ÿ—^½º[ÿE{™ÇGU]qü÷KBBHBf¾T%“’°‡„ „…MÅLf^&&3ã{B@PqÄ´­­€H­¸o€¢¨wkk[k­­¶ÚM»ÛM[{ÞË ,±ÌÀtþ˜ùÞ3çüÞ¹÷Ü{Þ{Š' +Ï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šÒ“ÏNJô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´‹‹héX#ÖVñcUŒêUèDDbtñŠ-b¹ãŽÑ{¡ü«Çè[/3‰8^–ÌʔÙEÄ7>Y«Xsky›ò_¬Ú¶¥]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­QmVPZRªÍ2z0Ü¡ÑªÂÁ6Ý¯Í ù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â!<ŒGð(öböã1<ŽxO⠞ÂÓx‡ðžÇaÁ x/áe¼‚Wñ^Çwð¾‹ïáM|?Àñ~Ä>ø1ÞÁOð.~ŠŸá=¼Ÿãøâ—ø~ßà·øãwø=þ€?âOø3þ‚OðWü Ç?˜Oñþ…ãs‚d&Þfû2›.º™Ã~ìÏÈAÌ!ÊaÎÓ8‚#©qGótžÁ39†gq,ÇñlžÃñÌ¥‡yÌg'p" YÄI,f K9™S8•Ó838“eœÅrVp6+9‡UœËyœÏj.` ²–‹XÇz6°‘‹¹„K¹ŒËy.Ïãù\Á ØD/›é£Ÿ:[`+ ®ä*ÙÆÌðBš´e;W³ƒkØɵ\Nj¸žx1/á¥ÜÈËx9¯à•¼ŠWó^ËMÜÌ븅×óÞț¸•Ûx3oáWøU~·òëüoãvîàNÞÎ]¼ƒ»ùMÞÉoñ.îáݼ‡÷ò>ÞÏø âÃ|„r/÷q?ãã<À'ø$ò)>Ígøm>ËC|ŽÏó0ð¾È—ø2_á«|¯ó?ÁPÀþC¶mÛÖªeÛ¶m×â²mÛ¶µeÛ¶µÝ Îçƒ ÁÅàRp9¸\ ®׃ÁÍàVp;¸Ü î÷ƒÁÃàQð8x< žσÁËàUð:x¼ ÞïƒÁÇàSð9ø| ¾߃ÁÏàWð;ø! #$B!4 ,Â!<" ""!2¢ *¢!:b &b!6â .â!> !!1’ )’!9R %R!5Ò -Ò!=2 #2!3² +²!;r 'r!7ò /ò!? +  +¡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öãâãŽâŽãNâNã þÁ¿8‹s8 ¸ˆK¸Œ+¸Šk¸Ž¸‰[¸;¸‹{¸xˆGxŒ'xŠgxŽx‰Wx7x‹wxøˆOøŒ/øŠoøŽø‰_øÿ‚AR4C2C3 Ã2Ã3#2#3 +£2£3c2c3ã2ã323 “2“3S2S3 Ó2Ó33233 ³2³3s2s3ó2ó³ ² óaQãŸ,Î,ÉR,Í2,Ër,Ï +¬È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žækj‘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ÐÞE¶mÛ¶mÛ¶mÛ¶m»yÙ¶m¬Ú9Á©àtp&8œ ΂‹Á¥àrp%¸\ ®7‚›Á­àvp'¸Ü Á£àqð$x< ž/‚—Á«àuð&x¼ Þ‚Á§àsð%ø| ¾ÿ?‚ŸÁ/„ B# Â"@F8„GDD$DFDE4DG ÄD,ÄFÄE<ÄG$D"$F$E2$G +¤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?ôÇ Ä Æ Å0 ÇŒÄ(ŒÆŒÅ8ŒÇLÄ$LÆLÅ4LÇ ÌÄ,ÌÆÌÅ<ÌÇ,Ä",Æ,Å2,Ç +¬Ä*¬Æ¬Å:¬Ç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Ç6`± ûáAØÆCðP< Ç#ðH< +Æ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Ø`è ŽÝèÁƒ!ú BŒ)V°{1Ä*ނ·âmx;Ã;ðN¼ ïÆ{ð^¼ïÇðA|ÆGðQ| Ç'ðI| +ŸÆgðY|ŸÇðE| _ÆWðU| _Ç7ðM| ßÆwð]|ßÇðCü?ÆOðSü ?Ç/ðKü +¿Æoð[ü¿ÇðGü Æ_ðWü Ç?ðOü ÿÆð_Ž#p$ŽÂÑ8Çâ8p"NÂÉ8§â4œŽ3p&ÎÂÙ8çâ<œ p!.Ÿ—â2\Ž+p%®ÂÕ¸×â:\p#nÂ͸·â6܎;p'îÂݸ÷â>܏¬}¬}­«bZU«fÕ­1‹X°¬F&lRïÐг+~:µ¡O£„SÏæÝnfSߧ ƒÜ£Ï§U›y ­«`£««l0ÉYeѪ½œ•'YéÈ^.Þ9¥J䆈3>]P¯šÃJ"¨©ä•¾ Õ?Ñ­†™Ðãk4ž\_d#4×)5×),×iTDB+ì #±SxǚÙÍJ<«1ijUmm;y ·êsF´yK±«¶é‰e¹• ¿™UíG¦Ë ”F¤á¬†3¶t¢]:veéZ|M~l£%lzúey ¥q»ŽÝŒ4ŒK¸¤I-ëñ®VÄ-óZK±&°ÙPmQûŒ/ºݛÜB#[lŸ²²ÆN•'ÃnbŽnlfo¯(f¶DÕ*E'ZŠ¶æ·¢¡£¡­!×k‰UÃCËÏ×ÉßÓ9™–Ÿiù™–¿ÜÂèëØLËÏ´ü‰–?ÕãZþž!bÈß1ä ùã5ò»ëåšòÇëågÿ'?×Eq-×gÔ6N•cœQmµ3ªæPC#‚gàÀ8£‰yF{ÆMמQÛX×ÉY–ö³ÛcipÖÆYP7¡nvµÝӈ™zOM +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„Ïô±Ä4q ‰¿p˜¶Ô_¿í % ß;ÊÀ‡n‚×_8Røi›ædFÑ ¢Ô¡Úpà&æ¥B‘äIú!Gž2¿~ Œˆ'Oú¬Ê}A’jè‚tHâ È{KÛHÂ+¾%=`¬«aEÚ҅ÀM$á<¿!çØøi«ÇJôpL"Úàè—Å‘žÜ{2'mÞîCì>¤gwfg¤Å&Oyl´ yú‡T74Z›n’iØt³N»Yßôeöe}ÓÍ:íf}ӓuzŽ~ ±1O&¿ú,0O½xŠûÝ/a7Þ½tCç]ज़ ÞÍÅK_.x_.^º¹àÝ\¼ôä‚÷ä¢:¨£q$¢è”A‘œ!Ꝙñkö q³É)¢ÎڔÇ\òê)åýy +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(¡jfàyf¦Hõ¥þï šk@÷]W^£’d¤Ój‰Ò:·ne‰Â-Ä: \˜] ^4U:1 c³mˆ æ“AÜ;VèB$0OírݛSRCj +~à:-×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 `-csXǖ„®¢uÕHøž¡‘6ëÖ¯¥«g2ñÆ÷±Ï3à#½šüÕl¤_³‘¯¸<½Ñ«?=iA"Qkðp!¨Ìbçˆt"9‰|¬_ó‘›f=øy‹ëGW]‰\5ªƒ¯·{V‘"'­”Þ80²y[hŠ;S¸ô¥µ:ƯÁ¶1 1pkÀk†¸åë¸ó¬KA‘­yô‘Y'ۏÕmσd5-``Å;Ÿ0žÃÕ!¼ºɒïà"+ +²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«ì!¬b #…Áï^êGµ…m[Éþÿ(Ù·•8'ÞÈޏp¨–”–?௥À-äik^æpF1Hj¸µ•ãEÃ+jàÛÌyƒDê9Dæ1øBßB¢%­h¸¨©0p<Â-®$Œg݅¸OæÁn%ãÎr[˜&ŽÃÎ^%âm ~0fL4±JJZÈá7æ§ÁŸ+›¢÷\¤{ÌJE¿¨*Ó;.èÀ%‘;U´q¸'LGS©ÀÏ1©¹„q³Îèjð ÿփ篃ç?žÿ2xþëàùoƒç¿žÿñéyõ'ø[ÁßÿØ®Öæ´u&<“3…Áv.Mû~yB§ÒÓ~L iÓ[ΤÉé9ýÒ1¶ÀƦ¾@áן•´–´"3!»ûhoZÉ+i¿!üFð»ç¼Ytu’ ì9ÁtÅölÎÉ9xÏJ½2ù-õJ¶M`?ä¤"Ÿ‚pEMۃŒ“<’ +«´.%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ÄkFå”ûÝmšHÌ´;V1,’}Ã"Ù3P£ôn{U° ˆõ3cÀtqNqåþeðƒÆPQ?ÆñCñFÝËÑ[Ô²xÛ|›dó,ŸÂ›KÌfÎm5r$Vœ¨e¬ÉYAªb5(Šp %H“ð`æ·®áöœÌz’ˆQäá¸fžäõQá €PŒø¾Ë¤ì$ü$à'I3°'ŠFtxÝc‰#Ε¼:ŒÚRþx$é2¡ õ•#nŽCÐÀ2¶è\C0!s®ÉI G ³æ¥9Ý*I#vðÑ?xðÏù7Z‹¯½…•ŠX q{H™ûoBæ~4+XƳ"ÈBŸß“ó;ã"ãeð¢8˜Íþ˜Íø¥ÇÛ<‚?ã‰q GEÆ0¼Ì Љañ@ ŠçnÀî”Uæsâxž,YJñþ3'f¦è®ƒ51ØÛ&jTԁ ³2å3x鈣(H܊g ÑX2òr³š„=)Áb“H+Çpíˆâž—¦,uÐÅmfLkâ@Œ—³"·oe]7ܽg¸UE Ž¹L,<Ž˜&Κ†XÛ!ÖÖU¦ +cbq"zWI $5ŒœâÃ- +";s\hŒ "M +rIËò<›Çu–†¿ ÁuN¥4¨`édn4'È<'é!D‚ž"h>WSxó2fX?WH¸ÂÀO¡ð{è ¼ï©î;8çÐ#0Ÿû¾c?’Åö1×Ð øå;‚»'4~Ž®žÁÏ$> xÏ·@áÄ…ž@xzdíA§\¶Õ¶{jgMµÍµ:kŠMkm+î[øoÍ¿JÛ°½›ú^á øT-AÕÚí¡ûImÉôÆÚ{-ˆ¥1—ì9¿µË'~ænåq°ÜÁÀšª#Jԟm‚Íc¸6à'°9°åUGó3yfðì3 M„‡0»}“yö‹ÎµŸr|­ƒbZWf—phËôìŽéVv­k·?¯²»_bÓdoɽ±=Œ¶ +jfe/° ± Ç{ÛÒ[X&mÉ0/Ú¢iKvwAb~vnɘ)s™ä9:Êض‰-#‰,á5 ÁÏ\R¬YýÈ֏,ýŠº² ]YUÛQý­¿³ôiÙ¼ ¡Ur EšÇÔâDôë‰2£›xfoâ™uêÿ¢ì/û€ýEXgAõ¶þÂ҇¾dêƒHõEß2¶ïÒúBö:·´¾×:Ž¹LMd5?*Z%ûÃu­ïց^gèŸËº(ë‚4zð¶¶×­Õ†`îF3^c×3Ñ)ﬖzÓ¢%ú? ÕúÏ͑iºÑ-ˆ­V‡èócVý*†K<Ê(ßd=%Õ«ÊÒ²Ÿ›RëA–xüã`¼;ˆv­÷bªb{ûW¯‘¾é^Š=͇ºbۗI&èÑM K¹@!7„Îe4ME!:AÃ9—IÆw ‹¦|ÏðÇ;eÁ”Å„Z0jq­,eqM-jq£,reqC-rjq«, +eqK- +jq¯,jeqO-j"Ššü Ò+I¯þ‰ô5Ò7H/‘¾Eúé)ú ¿áK¤>ÒÒ¡æðj>ãeõš¼¡gx"Å&_N…¶ÜÜ⦠X_`ô”økP¯Ù÷üÚ½àˆo«F8Ñí®ú"²’ª$”äÈÍ܈§+ޞV9tÝd Xw‘ƒ|«Ðv•ÏÌhi*xY½dE–É<ØS¿¬hŸ÷լ䇈;U8€ê ^¨åv9ÍSnóJ0îJÛd)[±üªDÖò«’ì[øîƒ0„#£;Ul‡£b¼3m¸îÛÎ&ÉNÛ©¦øòú¡)uÇÚw¤}•ïHùkߑbÇèí0BÆåEp„½Kl¡#f +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<ã ÐÿýÐÿýÐÿýp~œßã 0Þã 0Þ㠚x8¿Îoˆñ‡ˆñ‡ˆñ‡ˆñ‡ˆñ‡ˆñ‡ˆñ‡ˆñ‡„ñG„ñG„ñG„ñFo„ñFo„ñFïý_ ÿ ôþ/Ðÿú¿ÀùýÇw¹-·#a¸Ê±,É:ù”ɤ¶ö¶b;ÖáR*ñz'ޝMÅ7S ‰ˆxPHвõôÛ~R¤ÄÙýÄׁnàã»çýߤýÂÿ.úï¢ÿ.úï¢ÿ.úï¢ÿ.úï¢ÿ.ÆÓ§‹ñtÁëb<]p»˜¿.ø=ð{à÷Àïß¿~üø=ð{à÷Àïß¿~¼>x}ðúàõÁëƒ×¯^¼>x}ðúàõÁë§<Œ·ñÀ€?þüøðàÀ€?þüøðàÁ‚?þü!øCð‡àÁ‚?þü!øCð‡àÀ?þü¸#pGàŽÀ;wîܸ#pÇàŽÁƒ;w îÜ1¸cpÇàŽÁƒ;w îÜ1¸p'àNÀ›€?oÞ¼ xð&àMÀ™€3ÇÇÇÇÇÂø,üW \ \ \ \ \ \ ã´À·À·ÀŸ‚3w +Þœ)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úP|ÎV¨¨Ûe›L•úö3Œ]"þ=Ÿ$÷ßÚÿïí[:zÐhiI(ß2uQ¦^|-WeߓG¢éÍR‹˜»a4=³Øt>®x¹e[)Å¡GW|½Ó‚tjÚA¡MõÞÒݞ„>_˜ jൕ+´Yæ"Ö1ø4«mï_ÊzC3|†¤þÓ XÉ'$Ç¿&³s jZ>Íd +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Éà)"<å™q¾SÕUÝ=6yè®s¾ºsªê\B<êÈB*uĨ”ÛTǙsYä]qÝ$WÚ葟ˆÓ€4»á¤{ÓìF?ݛb«ƒ¡âHôÒ:ëº}eø’•bËë7Ó¼5Æ¿:Æï¥ùÊú8`åKc|oŒ¿<Æ;cüÖïñ×Æø ÍO˜ïæÖÉÄüqÏ] 7èI«Íُc å\‡…}3¾xæ äJì't”xi¹a³G5jY”nŠ¯ ž—g +,ñЯØ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Ú"ÿÉñ'…ð—™Ùtw(õ-òû/ûkä(pÒ8 –ÂAr +y¤$À¯„dæ;®Û˜‘¸ H%~Œpèé¹Xû¶ZºéÙV›©É8 ÓCˆŠŒ%qr\¸W\^ ‰¹2w<ÐÄë™CÚ%Ê{©ØˆÑš@qæí¬ûn϶ðLÓcEpzpÉÇé{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+gigµ㤱¢Mk ãýmBkŒÚèñöï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â\˅k¶b-@Ây01q˜P_»²P¸qÜmü”¦tÿA×­h ñµãN“È,ßÏûúùñ¾~Ÿ×þ9àO´…@6—§¿X’Xí„߫ӏ¨ûMjùž7^ø}Àï~Ðíðñ]j¹J kØ$SôÝ$/{›Ä~UÅ~UÅ~U…‹ªþ@kt‘%˜Ž/ð¢—á½Eo³"*}È] £>y×ö0æ6BÀ¨û‡Üæ¼Ç Á’G6ဗp†½]B'• +:±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Ù,TdI¥])Á¢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é?ý%kÈ endstream endobj 407 0 obj 34521 endobj 408 0 obj << /Type /Metadata /Subtype /XML /Length 2989 >> stream + + + + + + 2006-04-02T13:01:22+01:00 + 2006-04-02T13:01:23+01:00 + Adobe Illustrator 10.0.3 + Adobe PDF library 5.00 + + + + 2006-04-02T13:01:22+01:00 + 2006-04-02T13:01:23+01:00 + Adobe Illustrator 10.0.3 + 2006-04-02T13:01:23+01:00 + + + + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html new file mode 100644 index 000000000..97f56c6e2 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/util/package.html @@ -0,0 +1,11 @@ + + + Encoding Utilities + + +

+ This package contains a number of utility classes which can come handy + from time to time when writing Java code. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java new file mode 100644 index 000000000..e263eeb59 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVException.java @@ -0,0 +1,132 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A {@link RuntimeException} representing a + * WebDAV + * response for a specified {@link DAVResource}.

+ * + * @author Pier Fumagalli + */ +public class DAVException extends RuntimeException { + + private DAVResource resource = null; + private int status = 0; + + /** + *

Create a new {@link DAVException} instance.

+ */ + public DAVException(int status, String message) { + this(status, message, null, null); + } + + /** + *

Create a new {@link DAVException} instance.

+ */ + public DAVException(int status, String message, Throwable throwable) { + this(status, message, throwable, null); + } + + /** + *

Create a new {@link DAVException} instance.

+ */ + public DAVException(int status, String message, DAVResource resource) { + this(status, message, null, resource); + } + + /** + *

Create a new {@link DAVException} instance.

+ */ + public DAVException(int s, String m, Throwable t, DAVResource r) { + super(m, t); + this.resource = r; + this.status = s; + } + + /** + *

Return the status code associated with this instance.

+ */ + public int getStatus() { + return this.status; + } + + /** + *

Return the {@link DAVResource} associated with this instance.

+ */ + public DAVResource getResource() { + return this.resource; + } + + /** + *

Write the body of this {@link DAVException} to the specified + * {@link DAVTransaction}'s output.

+ */ + 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(""); + out.print("Error "); + out.print(message); + out.println(""); + out.println(""); + out.print("

Error "); + out.print(message); + out.println("

"); + + /* Check if we have a resource associated with the extension */ + if (this.getResource() != null) { + String r = transaction.lookup(this.getResource()).toASCIIString(); + out.print("

Resource in error: "); + out.print(r); + out.println("

"); + } + + /* Process any exception and its cause */ + Throwable throwable = this; + out.println("

Exception details:

"); + while (throwable != null) { + out.print("
");
+            throwable.printStackTrace(out);
+            out.println("
"); + throwable = throwable.getCause(); + if (throwable != null) out.println("

Caused by:

"); + } + + /* Close up the HTML */ + out.println(""); + out.println(""); + out.flush(); + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java new file mode 100644 index 000000000..6a0976c71 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVInputStream.java @@ -0,0 +1,165 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A specialized {@link InputStream} to read from {@link DAVResource}s.

+ * + *

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.

+ * + * @author Pier Fumagalli + */ +public class DAVInputStream extends InputStream { + + /**

The {@link InputStream} of the source {@link File}.

*/ + protected InputStream input = null; + /**

The {@link DAVResource} associated with this instance.

*/ + private DAVResource resource = null; + + /** + *

Create a new {@link DAVInputStream} instance.

+ */ + 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); + } + } + + /** + *

Read data from this {@link InputStream}.

+ */ + 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); + } + } + + /** + *

Read data from this {@link InputStream}.

+ */ + 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); + } + } + + /** + *

Read data from this {@link InputStream}.

+ */ + 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); + } + } + + /** + *

Skip a specified amount of data reading from this + * {@link InputStream}.

+ */ + 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); + } + } + + /** + *

Return the number of bytes that can be read or skipped from this + * {@link InputStream} without blocking.

+ */ + 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); + } + } + + /** + *

Return the number of bytes that can be read or skipped from this + * {@link InputStream} without blocking.

+ */ + 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; + } + } + + /** + *

Marks the current position in this {@link InputStream}.

+ */ + public void mark(int readlimit) { + if (this.input == null) throw new IllegalStateException("Closed"); + this.input.mark(readlimit); + } + + /** + *

Repositions this stream to the position at the time the + * {@link #mark(int)} method was last called on this + * {@link InputStream}.

+ */ + 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); + } + } + + /** + *

Tests if this {@link InputStream} supports the {@link #mark(int)} + * and {@link #reset()} methods.

+ */ + public boolean markSupported() { + if (this.input == null) throw new IllegalStateException("Closed"); + return this.input.markSupported(); + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java new file mode 100644 index 000000000..6357bcc16 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVListener.java @@ -0,0 +1,46 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A simple interface identifying a {@link DAVRepository} event listener.

+ * + * @author Pier Fumagalli + */ +public interface DAVListener { + + /**

An event representing the creation of a collection.

*/ + public static final int COLLECTION_CREATED = 1; + /**

An event representing the deletion of a collection.

*/ + public static final int COLLECTION_REMOVED = 2; + /**

An event representing the creation of a resource.

*/ + public static final int RESOURCE_CREATED = 3; + /**

An event representing the deletion of a resource.

*/ + public static final int RESOURCE_REMOVED = 4; + /**

An event representing the modification of a resource.

*/ + public static final int RESOURCE_MODIFIED = 5; + + /** + *

Notify this {@link DAVListener} of an action occurred on a + * specified {@link DAVResource}.

+ * + * @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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java new file mode 100644 index 000000000..9ba48466d --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVLogger.java @@ -0,0 +1,86 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A simplicisting class defining an esay way to log stuff to the + * {@link ServletContext}.

+ * + * @author Pier Fumagalli + */ +public class DAVLogger { + + private final ServletContext context; + private final String servletName; + private final boolean debug; + + /** + *

Create a new {@link DAVLogger} from a {@link ServletConfig}.

+ */ + public DAVLogger(ServletConfig config, boolean debug) { + this.context = config.getServletContext(); + this.servletName = config.getServletName(); + this.debug = debug; + } + + /** + *

Log a debug message to the context logger.

+ */ + public void debug(String message) { + if (this.debug) this.doLog(message, null); + } + + /** + *

Log a debug message and related exception to the context logger.

+ */ + public void debug(String message, Throwable throwable) { + if (this.debug) this.doLog(message, throwable); + } + + /** + *

Log a message to the context logger.

+ */ + public void log(String message) { + this.doLog(message, null); + } + + /** + *

Log a message and related exception to the context logger.

+ */ + public void log(String message, Throwable throwable) { + this.doLog(message, throwable); + } + + /** + *

Internal method for formatting messages and logging.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java new file mode 100644 index 000000000..e73eb52b5 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMethod.java @@ -0,0 +1,41 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

An interface describing the implementation of a + * WebDAV + * method.

+ * + * @author Pier Fumagalli + */ +public interface DAVMethod { + + /** + *

Process the specified {@link DAVTransaction}.

+ * + * @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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java new file mode 100644 index 000000000..e7c2fc5da --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVMultiStatus.java @@ -0,0 +1,149 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A {@link DAVException} representing a + * WebDAV + * 207 (Multi-Status) response.

+ * + * @author Pier Fumagalli + */ +public class DAVMultiStatus extends DAVException { + + private Set responses = new HashSet(); + + /** + *

Create a new {@link DAVMultiStatus} instance.

+ */ + public DAVMultiStatus() { + super(207, "Multi-Status response"); + } + + /** + *

Write the body of the multi-status response to the specified + * {@link DAVTransaction}'s output.

+ */ + 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(""); + out.println(""); + + Iterator responses = this.responses.iterator(); + while (responses.hasNext()) { + Response response = (Response) responses.next(); + out.println(" "); + out.print(" "); + out.print(transaction.lookup(response.resource)); + out.println(""); + + if (response.status != 0) { + out.print(" HTTP/1.1 "); + out.print(DAVUtilities.getStatusMessage(response.status)); + out.println(""); + } + + if (response.message != null) { + out.print(" "); + out.print(response.message); + out.println(""); + } + + out.println(" "); + } + + out.println(""); + out.flush(); + } + + /** + *

Return the number of responses held in this instance.

+ */ + public int size() { + return this.responses.size(); + } + + /** + *

Merge the responses held into the specified {@link DAVMultiStatus} + * into this instance.

+ */ + public void merge(DAVMultiStatus multistatus) { + if (multistatus == null) return; + Iterator iterator = multistatus.responses.iterator(); + while (iterator.hasNext()) this.responses.add(iterator.next()); + } + + /** + *

Merge the details held into the specified {@link DAVException} + * into this instance.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java new file mode 100644 index 000000000..2d6726551 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVNotModified.java @@ -0,0 +1,56 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A simple {@link DAVException} encapsulating an + * HTTP not modified + * response.

+ * + * @author Pier Fumagalli + */ +public class DAVNotModified extends DAVException { + + private DAVResource resource = null; + + /** + *

Create a new {@link DAVNotModified} instance.

+ */ + public DAVNotModified(DAVResource resource) { + super(304, "Resource Not Modified"); + this.resource = resource; + } + + /** + *

Write the body of this {@link DAVNotModified} to the specified + * {@link DAVTransaction}'s output.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java new file mode 100644 index 000000000..6a5c80601 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVOutputStream.java @@ -0,0 +1,187 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A specialized {@link OutputStream} to write to {@link DAVResource}s.

+ * + *

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.

+ * + *

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.

+ * + * @author Pier Fumagalli + */ +public class DAVOutputStream extends OutputStream { + + /**

The original resource {@link File}.

*/ + private File temporary = null; + /**

The {@link OutputStream} of the temporary {@link File}.

*/ + protected OutputStream output = null; + /**

The {@link DAVResource} associated with this instance.

*/ + private DAVResource resource = null; + + /** + *

Create a new {@link DAVOutputStream} instance.

+ */ + 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); + } + } + + /** + *

Rename the temporary {@link File} to the original one.

+ */ + 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"); + } + } + + /** + *

Abort any data written to the temporary file and delete it.

+ */ + 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; + } + } + + /** + *

Close this {@link OutputStream} {@link #rename(File,File) renaming} + * the temporary file to the {@link DAVResource#getFile() original} one.

+ */ + 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(); + } + } + + /** + *

Flush any unwritten data to the disk.

+ */ + 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); + } + } + + /** + *

Write data to this {@link OutputStream}.

+ */ + 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); + } + } + + /** + *

Write data to this {@link OutputStream}.

+ */ + 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); + } + } + + /** + *

Write data to this {@link OutputStream}.

+ */ + 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); + } + } + + /** + *

Finalize this {@link DAVOutputStream} instance.

+ */ + public void finalize() { + this.abort(); + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java new file mode 100644 index 000000000..d50b65875 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVProcessor.java @@ -0,0 +1,92 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

The WebDAV + * transactions processor.

+ * + * @author Pier Fumagalli + */ +public class DAVProcessor { + + /**

All the implemented methods, comma separated.

*/ + public static final String METHODS = "COPY,DELETE,GET,HEAD,MKCOL,MOVE," + + "OPTIONS,PROPFIND,PROPPATCH,PUT"; + + /**

A static map of all known webdav methods.

*/ + 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); + } + } + + /**

The {@link DAVRepository} associated with this instance.

*/ + private DAVRepository repository = null; + + /** + *

Create a new {@link DAVProcessor} instance.

+ */ + public DAVProcessor(DAVRepository repository) { + if (repository == null) throw new NullPointerException(); + this.repository = repository; + } + + /** + *

Process the specified {@link DAVTransaction} fully.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java new file mode 100644 index 000000000..aa3ed42d4 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVRepository.java @@ -0,0 +1,164 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A simple class representing a {@link File} based WebDAV repository.

+ * + * @author Pier Fumagalli + */ +public class DAVRepository { + + /**

A {@link String} of all acceptable characters in a URI.

*/ + private static final String ACCEPTABLE = + "ABCDEFGHIJLKMNOPQRSTUVWXYZ" + // ALPHA (UPPER) + "abcdefghijklmnopqrstuvwxyz" + // ALPHA (LOWER) + "0123456789" + // DIGIT + "_-!.~'()*" + // UNRESERVED + ",;:$&+=" + // PUNCT + "?/[]@"; // RESERVED + + + /**

The {@link File} identifying the root of this repository.

*/ + protected File root = null; + /**

The {@link URI} associated with the root of this repository.

*/ + protected URI base = null; + /**

The {@link Set} of all configured {@link DAVListener}s.

*/ + private Set listeners = new HashSet(); + + /** + *

Create a new {@link DAVRepository} instance.

+ * + * @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 null. + */ + 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"); + } + } + + /** + *

Return the {@link URI} representing the root directory of this + * {@link DAVRepository}.

+ * + * @return a non-null {@link URI} instance. + */ + protected URI getRepositoryURI() { + return (this.base); + } + + /** + *

Return the {@link DAVResource} associated with the given name.

+ * + * @param name a {@link String} identifying the resource name. + * @return a non-null {@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); + } + } + + /** + *

Return the {@link DAVResource} associated with a {@link URI}.

+ * + *

If the specified {@link URI} is relative it will be resolved against + * the root of this {@link DAVRepository}.

+ * + * @param uri an absolute or relative {@link URI} identifying the resource. + * @return a non-null {@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()); + } + + /** + *

Add a new {@link DAVListener} to the list of instances notified by + * this {@link DAVRepository}.

+ */ + public void addListener(DAVListener listener) { + if (listener != null) this.listeners.add(listener); + } + + /** + *

Remove a {@link DAVListener} from the list of instances notified by + * this {@link DAVRepository}.

+ */ + public void removeListener(DAVListener listener) { + if (listener != null) this.listeners.remove(listener); + } + + /** + *

Notify all configured {@link DAVListener}s of an event.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java new file mode 100644 index 000000000..f2d0911ee --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVResource.java @@ -0,0 +1,514 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A simple representation of a WebDAV resource based on {@link File}s.

+ * + * @author Pier Fumagalli + */ +public class DAVResource implements Comparable { + + /**

The mime type when {@link #isCollection()} is true.

*/ + public static final String COLLECTION_MIME_TYPE = "httpd/unix-directory"; + + /**

The prefix for all temporary resources.

*/ + protected static final String PREFIX = ".dav_"; + /**

The suffix for all temporary resources.

*/ + protected static final String SUFFIX = ".temp"; + /**

The {@link DAVRepository} instance containing this resource.

*/ + private DAVRepository repository = null; + /**

The {@link File} associated with this resource.

*/ + private File file = null; + + /* ====================================================================== */ + /* Constructors */ + /* ====================================================================== */ + + /** + *

Create a new {@link DAVResource} instance.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return an integer number for the hash value of this instance.

+ */ + public int hashCode() { + return this.file.hashCode(); + } + + /** + *

Compare this instance to another object for equality.

+ */ + 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); + } + } + + /** + *

Compare this instance to another object for sorting.

+ */ + public int compareTo(Object object) { + DAVResource resource = (DAVResource) object; + return (this.file.compareTo(resource.file)); + } + + /* ====================================================================== */ + /* Resource checkers */ + /* ====================================================================== */ + + /** + *

Checks if this {@link DAVResource} is a null (non existant) one.

+ * + * @return true if this resource does not esist (is a null resource). + */ + public boolean isNull() { + return (! this.file.exists()); + } + + /** + *

Checks if this {@link DAVResource} is a collection.

+ * + * @return true if this resource is a collection. + */ + public boolean isCollection() { + if (this.isNull()) return false; + return (this.file.isDirectory()); + } + + /** + *

Checks if this {@link DAVResource} is an existing resource.

+ * + * @return true if this resource is a collection. + */ + public boolean isResource() { + if (this.isNull()) return false; + return (! this.isCollection()); + } + + /* ====================================================================== */ + /* Resource methods */ + /* ====================================================================== */ + + /** + *

Return the {@link File} associated with this resource.

+ */ + protected File getFile() { + return this.file; + } + + /** + *

Return the {@link DAVRepository} associated with this resource.

+ */ + public DAVRepository getRepository() { + return this.repository; + } + + /** + *

Return the bare name of this resource (without any "/" + * slashes at the end if it is a collection).

+ * + * @return a non null {@link String}. + */ + public String getName() { + return this.file.getName(); + } + + /** + *

Return the display name of this resource (with an added "/" + * slash at the end if it is a collection).

+ * + * @return a non null {@link String}. + */ + public String getDisplayName() { + String name = this.getName(); + if (this.isCollection()) return (name + "/"); + return name; + } + + /** + *

Return the path of this {@link DAVResource} relative to the root + * of the associated {@link DAVRepository}.

+ * + * @return a non null {@link String}. + */ + public String getRelativePath() { + return this.getRelativeURI().toASCIIString(); + } + + /** + *

Return the {@link URI} of this {@link DAVResource} relative to the + * root of the associated {@link DAVRepository}.

+ * + * @return a non-null {@link URI} instance. + */ + public URI getRelativeURI() { + URI uri = this.file.toURI(); + return this.repository.getRepositoryURI().relativize(uri).normalize(); + } + + /** + *

Return the parent {@link DAVResource} of this instance.

+ * + * @return a non-null {@link DAVResource} instance or null + * 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; + } + } + + /** + *

Return an {@link Iterator} over all children of this instance.

+ * + * @return a non-null {@link Iterator} instance or null 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 */ + /* ====================================================================== */ + + /** + *

Return the MIME Content-Type of this {@link DAVResource}.

+ * + *

If the {@link #isCollection()} method returns true this + * method always returns text/html.

+ * + * @return a {@link String} instance or null 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()); + } + + /** + *

Return the MIME Content-Length of this {@link DAVResource}.

+ * + * @return a {@link Long} instance or null 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()); + } + + /** + *

Return the creation date of this {@link DAVResource}.

+ * + *

As this implementation relies on a {@link File} backend, this method + * will always return the same as {@link #getLastModified()}.

+ * + * @return a {@link String} instance or null if this resource does + * not exist. + */ + public Date getCreationDate() { + if (this.isNull()) return null; + return new Date(this.file.lastModified()); + } + + /** + *

Return the last modification date of this {@link DAVResource}.

+ * + * @return a {@link String} instance or null if this resource does + * not exist. + */ + public Date getLastModified() { + if (this.isNull()) return null; + return new Date(this.file.lastModified()); + } + + /** + *

Return a {@link String} representing the Entity Tag of this + * {@link DAVResource} as described by the + * HTTP RFC.

+ * + * @return a {@link String} instance or null 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 */ + /* ====================================================================== */ + + /** + *

Delete this resource.

+ * + * @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); + } + } + } + + /** + *

Copy this resource to the specified destination.

+ * + * @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; + } + } + + /** + *

Moves this resource to the specified destination.

+ * + * @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(); + } + + /** + *

Create a collection identified by this {@link DAVResource}.

+ * + *

This resource must be {@link #isNull() non-null} and its + * {@link #getParent() parent} must be accessible and be a + * {@link #isCollection() collection}.

+ * + * @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); + } + + /** + *

Return an {@link InputStream} reading the resource.

+ * + * @return a non-null {@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); + } + + /** + *

Return a {@link DAVOutputStream} writing to this {@link DAVResource} + * instance.

+ * + * @return a non-null {@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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java new file mode 100644 index 000000000..de36a6f6c --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVServlet.java @@ -0,0 +1,280 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A very simple servlet capable of processing very simple + * WebDAV + * requests.

+ * + * @author Pier Fumagalli + */ +public class DAVServlet implements Servlet, DAVListener { + + /**

The {@link DAVRepository} configured for this instance.

*/ + protected DAVRepository repository = null; + /**

The {@link DAVLogger} configured for this instance.

*/ + protected DAVLogger logger = null; + /**

The {@link DAVProcessor} configured for this instance.

*/ + protected DAVProcessor processor = null; + /**

The {@link ServletContext} associated with this instance.

*/ + private ServletContext context = null; + /**

The {@link ServletConfig} associated with this instance.

*/ + private ServletConfig config= null; + + /** + *

Create a new {@link DAVServlet} instance.

+ */ + public DAVServlet() { + super(); + } + + /** + *

Initialize this {@link Servlet} instance.

+ * + *

The only initialization parameter required by this servlet is the + * "rootPath" parameter specifying the path + * of the repository root (either absolute or relative to the configured + * {@link ServletContext}.

+ * + *

If the specified root is relative, it will be considered to + * be relative to the {@link ServletContext} deployment path.

+ * + *

In any case, the specified root must ultimately point to an existing + * directory on a locally-accessible file system.

+ * + *

When set to true, an optional parameter called + * xmlOnly will force this {@link DAVServlet} to use an + * {@link XMLRepository} instead of the default {@link DAVRepository}.

+ * + *

Finally, when set to true, the optional parameter + * debugEnabled will enable logging of method invocation and + * events in the repository.

+ */ + 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); + } + + /** + *

Retrieve a {@link DAVRepository} for a given {@link File}.

+ */ + public DAVRepository getRepository(File root) + throws IOException { + return new XMLRepository(root); + } + + /** + *

Detroy this {@link Servlet} instance.

+ */ + public void destroy() { + this.repository.removeListener(this); + } + + /** + *

Return the {@link ServletConfig} associated with this instance.

+ */ + public ServletConfig getServletConfig() { + return (this.config); + } + + /** + *

Return the {@link ServletContext} associated with this instance.

+ */ + public ServletContext getServletContext() { + return (this.context); + } + + /** + *

Return a informative {@link String} about this servlet.

+ */ + public String getServletInfo() { + return DAVUtilities.SERVLET_INFORMATION; + } + + /** + *

Execute the current request.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Receive notification of an event occurred in a specific + * {@link DAVRepository}.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Retrieve the key in the {@link ServletContext} where the instance of + * the {@link DAVRepository} associated with a named {@link DAVServlet} + * can be found.

+ * + * @param servletName the name of the {@link DAVServlet} as specified in + * the web.xml deployment descriptor.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java new file mode 100644 index 000000000..20089f022 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVTransaction.java @@ -0,0 +1,280 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A simple wrapper isolating the Java Servlet API from this + * WebDAV + * implementation.

+ * + * @author Pier Fumagalli + */ +public class DAVTransaction { + + /** + *

The identifyication of the infinity value + * in the Depth header.

+ */ + public static final int INFINITY = Integer.MAX_VALUE; + + /**

The nested {@link HttpServletRequest}.

*/ + private HttpServletRequest request = null; + /**

The nested {@link HttpServletResponse}.

*/ + private HttpServletResponse response = null; + /**

The {@link URI} associated with the base of the repository.

*/ + private URI base = null; + /**

The status for the HTTP response.

*/ + private int status = -1; + + /* ====================================================================== */ + /* Constructors */ + /* ====================================================================== */ + + /** + *

Create a new {@link DAVTransaction} instance.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Return the path originally requested by the client.

+ */ + public String getMethod() { + return this.request.getMethod(); + } + + /** + *

Return the path originally requested by the client.

+ */ + 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; + } + } + + /** + *

Return the path originally requested by the client.

+ */ + public String getNormalizedPath() { + final String path = this.getOriginalPath(); + if (! path.endsWith("/")) return path; + return path.substring(0, path.length() - 1); + } + + /** + *

Return the depth requested by the client for this transaction.

+ */ + 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); + } + } + + /** + *

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; + } + + /** + *

Return the overwrite flag requested by the client for this + * transaction.

+ */ + 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"); + } + + /** + *

Check if the client requested a date-based conditional operation.

+ */ + 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 */ + /* ====================================================================== */ + + /** + *

Set the HTTP status code of the response.

+ */ + public void setStatus(int status) { + this.response.setStatus(status); + this.status = status; + } + + /** + *

Set the HTTP status code of the response.

+ */ + public int getStatus() { + return this.status; + } + + /** + *

Set the HTTP Content-Type header.

+ */ + public void setContentType(String type) { + this.response.setContentType(type); + } + + /** + *

Set an HTTP header in the response.

+ */ + public void setHeader(String name, String value) { + this.response.setHeader(name, value); + } + + /* ====================================================================== */ + /* I/O methods */ + /* ====================================================================== */ + + /** + *

Check if there is a body in the request.

+ * + *

This method differs from checking if the return value of the + * {@link #read()} method is not null as a request body of length + * zero will return false in this case, while in the {@link #read()} + * method will return an empty {@link InputStream}.

+ */ + 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; + } + + /** + *

Read from the body of the original request.

+ */ + 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; + } + + /** + *

Write the body of the response.

+ */ + public OutputStream write() + throws IOException { + return this.response.getOutputStream(); + } + + /** + *

Write the body of the response.

+ */ + public PrintWriter write(String encoding) + throws IOException { + return new PrintWriter(new OutputStreamWriter(this.write(), encoding)); + } + + /* ====================================================================== */ + /* Lookup methods */ + /* ====================================================================== */ + + /** + *

Look up the final URI of a {@link DAVResource} as visible from the + * HTTP client requesting this transaction.

+ */ + public URI lookup(DAVResource resource) { + URI uri = resource.getRelativeURI(); + return this.base.resolve(uri).normalize(); + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java new file mode 100644 index 000000000..5e03f1e76 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/DAVUtilities.java @@ -0,0 +1,420 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

A collection of static utilities.

+ * + * @author Pier Fumagalli + */ +public final class DAVUtilities { + + /**

A {@link HashMap} of configured mime types.

*/ + private static Map MIME_TYPES = new HashMap(); + /**

A {@link HashMap} of configured mime types.

*/ + private static Properties PROPERTIES = new Properties(); + /**

The {@link SimpleDateFormat} RFC-822 date format.

*/ + private static final String FORMAT_822 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; + /**

The {@link SimpleDateFormat} RFC-822 date format.

*/ + private static final String FORMAT_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + /**

The {@link TimeZone} to use for dates.

*/ + private static final TimeZone TIMEZONE = TimeZone.getTimeZone("GMT"); + /**

The {@link Locale} to use for dates.

*/ + private static final Locale LOCALE = Locale.US; + + /** + *

Load the mime types map from a resource.

+ */ + 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); + } + } + + /**

The signature of this package usable from a servlet.

*/ + public static final String SERVLET_SIGNATURE = + DAVUtilities.getProperty("servlet.signature") + '/' + + DAVUtilities.getProperty("version"); + + /**

The information detail of this package usable from a servlet.

*/ + public static final String SERVLET_INFORMATION = + DAVUtilities.getProperty("servlet.information") + " version " + + DAVUtilities.getProperty("version"); + + /** + *

Deny public construction of {@link DAVUtilities} instances.

+ */ + private DAVUtilities() { + super(); + } + + /** + *

Return the value of a property configured for this package.

+ * + * @param name the property name + * @return a {@link String} instance or null if unknown. + */ + public static String getProperty(String name) { + if (name == null) return null; + return DAVUtilities.PROPERTIES.getProperty(name); + } + + /** + *

Return the MIME Type configured for a given resource.

+ * + * @param name the resource name whose MIME Type needs to be looked up. + * @return a {@link String} instance or null 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; + } + + /** + *

Return a {@link String} message given an HTTP status code.

+ */ + 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; + } + } + + /** + *

Format a {@link Number} into a {@link String} making sure that + * {@link NullPointerException}s are not thrown.

+ * + * @param number the {@link Number} to format. + * @return a {@link String} instance or null if the object was null. + */ + public static String formatNumber(Number number) { + if (number == null) return null; + return (number.toString()); + } + + /** + *

Parse a {@link String} into a {@link Long}.

+ * + * @param string the {@link String} to parse. + * @return a {@link Long} instance or null 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; + } + } + + /** + *

Format a {@link Date} according to the HTTP/1.1 RFC.

+ * + * @param date the {@link Date} to format. + * @return a {@link String} instance or null 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); + } + + /** + *

Format a {@link Date} according to the ISO 8601 specification.

+ * + * @param date the {@link Date} to format. + * @return a {@link String} instance or null 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); + } + + /** + *

Parse a {@link String} into a {@link Date} according to the + * HTTP/1.1 RFC (Mon, 31 Jan 2000 11:59:00 GMT).

+ * + * @param string the {@link String} to parse. + * @return a {@link Date} instance or null 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; + } + } + + /** + *

Parse a {@link String} into a {@link Date} according to the ISO 8601 + * specification (2000-12-31T11:59:00Z).

+ * + * @param string the {@link String} to parse. + * @return a {@link Date} instance or null 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; + } + } + + /** + *

Return the HEX representation of an array of bytes.

+ * + * @param buffer the array of bytes to convert in a HEX {@link String}. + * @return a non-null {@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); + } + + /** + *

Return the HEX representation of a long integer.

+ * + * @param number the long to convert in a HEX {@link String}. + * @return a non-null 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); + } + + /** + *

Return the HEX representation of an integer.

+ * + * @param number the int to convert in a HEX {@link String}. + * @return a non-null 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); + } + + /** + *

Return the HEX representation of a char.

+ * + * @param number the char to convert in a HEX {@link String}. + * @return a non-null 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); + } + + /** + *

Return the HEX representation of a byte.

+ * + * @param number the byte to convert in a HEX {@link String}. + * @return a non-null 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); + } + + /** + *

Return the single digit character representing the HEX encoding of + * the lower four bits of a given integer.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java new file mode 100644 index 000000000..57d34c6dc --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/XMLRepository.java @@ -0,0 +1,113 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

A {@link DAVRepository} instance enforcing all {@link DAVResource}s to + * be XML files.

+ * + * @author Pier Fumagalli + */ +public class XMLRepository extends DAVRepository { + + /** + *

Create a new {@link XMLRepository} instance.

+ */ + public XMLRepository(File root) + throws IOException { + super(root); + } + + /** + *

Return the {@link DAVResource} associated with a {@link URI}.

+ */ + public DAVResource getResource(URI uri) + throws IOException { + return new XMLResource(this, super.getResource(uri)); + } + + /** + *

A simple {@link DAVResource} extension enforcing XML writes.

+ */ + private static final class XMLResource extends DAVResource { + + /** + *

Create a new {@link XMLResource} instance.

+ */ + public XMLResource(XMLRepository repository, DAVResource resource) { + super(repository, resource.getFile()); + } + + /** + *

Override the MIME Content-Type to text/xml for + * normal resources.

+ */ + public String getContentType() { + if (this.isResource()) return "text/xml"; + return super.getContentType(); + } + + /** + *

Return a {@link DAVOutputStream} enforcing XML formatted data.

+ */ + public DAVOutputStream write() { + return new XMLOutputStream(this); + } + } + + /** + *

A simple {@link DAVOutputStream} enforcing XML formatted data.

+ */ + private static final class XMLOutputStream extends DAVOutputStream { + + /** + *

Create a new {@link XMLOutputStream} instance.

+ */ + protected XMLOutputStream(XMLResource resource) { + super(resource); + } + + /** + *

Ensure that whatever is in the temporary file is XML.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java new file mode 100644 index 000000000..c598bdc23 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/COPY.java @@ -0,0 +1,71 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * COPY metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class COPY implements DAVMethod { + + /** + *

Create a new {@link COPY} instance.

+ */ + public COPY() { + super(); + } + + /** + *

Process the COPY method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java new file mode 100644 index 000000000..37192a8ee --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/DELETE.java @@ -0,0 +1,54 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * DELETE metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class DELETE implements DAVMethod { + + /** + *

Create a new {@link DELETE} instance.

+ */ + public DELETE() { + super(); + } + + /** + *

Process the DELETE method.

+ */ + public void process(DAVTransaction transaction, DAVResource resource) + throws IOException { + try { + resource.delete(); + transaction.setStatus(204); + } catch (DAVMultiStatus multistatus) { + multistatus.write(transaction); + } + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java new file mode 100644 index 000000000..e7dc3c66b --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/GET.java @@ -0,0 +1,144 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

HTTP + * GET metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class GET extends HEAD { + + /**

The encoding charset to repsesent collections.

*/ + public static final String ENCODING = "UTF-8"; + + /**

The mime type that {@link GET} will use serving collections.

*/ + public static final String COLLECTION_MIME_TYPE = "text/html ;charset=\"" + + ENCODING + "\""; + + /** + *

Create a new {@link GET} instance.

+ */ + public GET() { + super(); + } + + /** + *

Process the GET method.

+ */ + 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(""); + out.println(""); + out.println("Collection: /" + path + ""); + out.println(""); + out.println(""); + out.println("

Collection: /" + path + "

"); + out.println(""); + out.println(""); + out.println(""); + out.println(""); + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java new file mode 100644 index 000000000..029437f12 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/HEAD.java @@ -0,0 +1,79 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

HTTP + * HEAD metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class HEAD implements DAVMethod { + + /** + *

Create a new {@link HEAD} instance.

+ */ + public HEAD() { + super(); + } + + /** + *

Process the HEAD method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java new file mode 100644 index 000000000..167d4a167 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MKCOL.java @@ -0,0 +1,57 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * MKCOL metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class MKCOL implements DAVMethod { + + /** + *

Create a new {@link MKCOL} instance.

+ */ + public MKCOL() { + super(); + } + + /** + *

Process the MKCOL method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java new file mode 100644 index 000000000..672636beb --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/MOVE.java @@ -0,0 +1,80 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * MOVE metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class MOVE implements DAVMethod { + + /** + *

Create a new {@link MOVE} instance.

+ */ + public MOVE() { + super(); + } + + /** + *

Process the MOVE method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java new file mode 100644 index 000000000..0228c09dc --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/OPTIONS.java @@ -0,0 +1,52 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

HTTP + * OPTIONS metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class OPTIONS implements DAVMethod { + + /** + *

Create a new {@link OPTIONS} instance.

+ */ + public OPTIONS() { + super(); + } + + /** + *

Process the OPTIONS method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java new file mode 100644 index 000000000..3854f20f0 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPFIND.java @@ -0,0 +1,122 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * PROPFIND metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class PROPFIND implements DAVMethod { + + /** + *

Create a new {@link PROPFIND} instance.

+ */ + public PROPFIND() { + super(); + } + + /** + *

Process the PROPFIND method.

+ */ + 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(""); + out.println(""); + + /* 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(""); + out.flush(); + } + + private void process(DAVTransaction txn, PrintWriter out, DAVResource res) { + /* The href of the resource is only the absolute path */ + out.println(" "); + out.println(" " + txn.lookup(res).getPath() + ""); + out.println(" "); + out.println(" "); + + /* Figure out what we're dealing with here */ + if (res.isCollection()) { + this.process(out, "resourcetype", ""); + } + 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(" "); + out.println(" HTTP/1.1 200 OK"); + out.println(" "); + out.println(" "); + } + + private void process(PrintWriter out, String name, String value) { + if (value == null) return; + out.print(" "); + out.print(value); + out.print(""); + } + +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java new file mode 100644 index 000000000..fbddd0e84 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PROPPATCH.java @@ -0,0 +1,55 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * PROPPATCH metohd implementation.

+ * + *

As this servlet does not handle the creation of custom properties, this + * method will always fail with a 403 (Forbidden).

+ * + * @author Pier Fumagalli + */ +public class PROPPATCH implements DAVMethod { + + /** + *

Create a new {@link PROPPATCH} instance.

+ */ + public PROPPATCH() { + super(); + } + + /** + *

Process the PROPPATCH method.

+ * + *

As this servlet does not handle the creation of custom properties, + * this method will always fail with a 403 (Forbidden).

+ */ + public void process(DAVTransaction transaction, DAVResource resource) + throws IOException { + throw new DAVException(403, "All properties are immutable"); + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java new file mode 100644 index 000000000..fb995e69d --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/PUT.java @@ -0,0 +1,72 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + + +/** + *

WebDAV + * PUT metohd implementation.

+ * + * @author Pier Fumagalli + */ +public class PUT implements DAVMethod { + + /** + *

Create a new {@link PUT} instance.

+ */ + public PUT() { + super(); + } + + /** + *

Process the PUT method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html new file mode 100644 index 000000000..f74f01889 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/methods/package.html @@ -0,0 +1,12 @@ + + + Could.IT WebDAV Servlet + + +

+ This package contains the implementation of all Level 1 + WebDAV + methods provided by this framework. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html new file mode 100644 index 000000000..1354f9f33 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/package.html @@ -0,0 +1,134 @@ + + + Could.IT WebDAV Servlet + + +

+ This package contains a minimal + Servlet + based implementation of the + WebDAV + specification. +

+

+ This implementation does not in any way try to replace or extend the + Apache Slide + WebDAV + implementation, but tries to provide a very light and extremely + minimal alternative to be used in those scenarios where space is + a constraint (the .jar file is less than 100 kylobites), + and advanced features are not required. +

+

+ The most visible limitations of this approach is that this + implementation does not offer any support for the LOCK + method (it is therefore not DAV Level 2 compliant), and that + there limited support for properties: +

+
    +
  • + The PROPFIND will only return the read-only + getcontenttype, getlastmodified, + getcontentlength, getetag and + resourcetype properties. +
  • +
  • + The PROPPATCH will always fail with a + 403 Not Found error. +
  • +
+

+ 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 + Apache Slide. +

+ +

Configuration

+

+ The main entry point of this implementation is defined in the + {@link it.could.webdav.DAVServlet} class, which will handle all + HTTP and + WebDAV requests + for the URI path it is configured to handle. +

+

+ To operate properly the {@link it.could.webdav.DAVServlet} class + must be configured in the web-application's web.xml + deployment descriptor. The relevant parts of a snippet of an example + configuration deployment descriptor might look like the following: +

+
+<servlet>
+  <servlet-name>dav</servlet-name>
+  <servlet-class>it.could.webdav.DAVServlet</servlet-class>
+  <init-param>
+    <param-name>rootPath</param-name>
+    <param-value>dav</param-value>
+  </init-param>
+  <init-param>
+    <param-name>xmlOnly</param-name>
+    <param-value>false</param-value>
+  </init-param>
+  <init-param>
+    <param-name>debugEnabled</param-name>
+    <param-value>false</param-value>
+  </init-param>
+  <load-on-startup>1</load-on-startup>
+</servlet>
+
+...
+
+<servlet-mapping>
+  <servlet-name>dav</servlet-name>
+  <url-pattern>/dav/*</url-pattern>
+</servlet-mapping>
+    
+

+ In this example the {@link it.could.webdav.DAVServlet} servlet + is configured with all parameters its parameters: +

+
+
rootPath
+
+ [required] This parameter indicates the path of the root of the + repository.
+ If the specified parameter represents a relative path, it will be + treated as a {@link javax.servlet.ServletContext#getResource(String) + ServletContext resource}.
+ Note that if you choose to distribute your web application in a + .war archive, your container will have to expand it + before intializing the {@link javax.servlet.ServletContext} as this + this implementation requires a {@link java.io.File} based + repository. +
+
xmlOnly
+
+ [optional, default="false"] This parameter + will instruct the {@link it.could.webdav.DAVServlet} to create + a very specialized version of the repository accepting only + well-formed + XML resources and collections.
+ Note that when set to true this implementation will rely + on the JAXP specification + to access a XML parser used to verify the PUT content. +
+
debugEnabled
+
+ [optional, default="false"] 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}. +
+
+

+ 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 /dav/ will be handled by this + implementation, with a repository rooted in the /dav/ + directory of the web application. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java new file mode 100644 index 000000000..56fc2e499 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplica.java @@ -0,0 +1,242 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

TODO: Document this class.

+ * + * @author Pier Fumagalli + */ +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/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java new file mode 100644 index 000000000..c7de67b14 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/DAVReplicator.java @@ -0,0 +1,131 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

The {@link DAVReplicator} class is a {@link DAVListener} replicating + * all content to the WebDAV repository specified at construction.

+ * + * @author Pier Fumagalli + */ +public class DAVReplicator extends HttpServlet { + + /**

The {@link DAVReplica} instances managed by this.

*/ + private final List replicas = new ArrayList(); + + /** + *

Create a new {@link DAVServlet} instance.

+ */ + public DAVReplicator() { + super(); + } + + /** + *

Initialize this {@link Servlet} instance.

+ * + *

This servlet requires a couple of initialization parameters: the + * first one is "repository" indicating the name of + * the {@link DAVServlet} in the "web.xml" deployment + * descriptor whose repository should be replicated.

+ * + *

The second required parameter "replicas" + * must contain a (whitespace separated list of) URL(s) where the original + * repository should be replicated to.

+ * + *

Finally, when set to true, the optional parameter + * debugEnabled will enable logging of method invocation and + * events in the repository.

+ */ + 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"); + } + + /** + *

Destroy {@link DAVServlet} instance interrupting all running + * {@link DAVReplica} instances.

+ */ + public void destroy() { + for (Iterator iter = this.replicas.iterator(); iter.hasNext() ; ) { + ((DAVReplica) iter.next()).interrupt(); + } + } +} diff --git a/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html new file mode 100644 index 000000000..4ca6e5c1c --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/it/could/webdav/replication/package.html @@ -0,0 +1,12 @@ + + + Could.IT WebDAV Servlet + + +

+ This package contains a framework for maintaining fully replicated + WebDAV + repositories. +

+ + \ No newline at end of file diff --git a/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/AbstractDavServerComponent.java new file mode 100644 index 000000000..c674f6b9e --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerComponent.java new file mode 100644 index 000000000..db4389f46 --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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 ); + + /** + *

+ * Flag to indicate how the dav server component should treat a GET request against + * a DAV Collection. + *

+ * + *

+ * 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. + *

+ * + *

+ * If false, the collection will always be presented in as a list of contents. + *

+ * + * @return true to use the index.html instead of directory contents. + */ + public boolean isUseIndexHtml(); + + /** + *

+ * Flag to indicate how the dav server component should treat a GET request against + * a DAV Collection. + *

+ * + *

+ * 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. + *

+ * + *

+ * If false, the collection will always be presented in as a list of contents. + *

+ * + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerException.java new file mode 100644 index 000000000..893f059ef --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerListener.java new file mode 100644 index 000000000..251cee6ad --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DavServerManager.java new file mode 100644 index 000000000..7c86cd533 --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java b/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/DefaultDavServerManager.java new file mode 100644 index 000000000..b66edee3f --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/AbstractWebDavServlet.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/DavServerRequest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicDavServerRequest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/basic/BasicWebDavServlet.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequest.java b/springy/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/springy/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; + +/** + *

+ * MultiplexedDavServerRequest - For requests that contain the server prefix information within the requested + * servlet's pathInfo parameter (as the first path entry). + *

+ *

+ *

+ * You would use this dav server request object when you are working with a single servlet that is handling + * multiple dav server components. + *

+ * + * @author Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedWebDavServlet.java b/springy/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/springy/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; + +/** + *

+ * MultiplexedWebDavServlet - and abstracted multiplexed webdav servlet. + *

+ * + *

+ * 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. + *

+ * + * @author Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/HackedMoveMethod.java b/springy/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/springy/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 Joakim Erdfelt (Hacked Version) + * @version $Id: HackedMoveMethod.java 6000 2007-03-04 22:01:49Z joakime $ + */ +public class HackedMoveMethod + implements DAVMethod +{ + + public HackedMoveMethod() + { + super(); + } + + /** + *

Process the MOVE method.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/ReplacementGetMethod.java b/springy/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/springy/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 Joakim Erdfelt (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 +{ + /**

The encoding charset to repsesent collections.

*/ + public static final String ENCODING = "UTF-8"; + + /**

The mime type that {@link ReplacementGetMethod} will use serving index.html files.

*/ + public static final String HTML_MIME_TYPE = "text/html"; + + /**

The mime type that {@link ReplacementGetMethod} will use serving collections.

*/ + public static final String COLLECTION_MIME_TYPE = HTML_MIME_TYPE + "; charset=\"" + ENCODING + "\""; + + /**

The header for content disposition.

*/ + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + + /**

The content-disposition for fancy-indexing.

*/ + public static final String INLINE_INDEX_HTML = "inline; filename=\"index.html\""; + + /** + * @plexus.requirement + */ + private MimeTypes mimeTypes; + + private boolean useIndexHtml = false; + + /** + *

Create a new {@link ReplacementGetMethod} instance.

+ */ + public ReplacementGetMethod() + { + super(); + } + + /** + *

Process the GET method.

+ */ + 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( "" ); + out.println( "" ); + out.println( "Collection: /" + path + "" ); + out.println( "" ); + out.println( "" ); + out.println( "

Collection: /" + path + "

" ); + out.println( "" ); + out.println( "" ); + out.println( "" ); + out.println( "" ); + 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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponent.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/MimeTypes.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WebdavMethodUtil.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java b/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java new file mode 100644 index 000000000..4b25e84c8 --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/DAVServlet.java @@ -0,0 +1,57 @@ +/* ========================================================================== * + * Copyright (C) 2004-2006, Pier Fumagalli * + * 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 . * + * * + * 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; + +/** + *

The {@link DAVServlet} class has been moved to a new package and should + * now be referred as {@link it.could.webdav.DAVServlet}.

+ * + *

This class will be preserved for some time (not so long) to give people + * time to update their servlet deployment descriptors.

+ * + * @author Pier Fumagalli + * @deprecated This class has been moved into the it.could.webdav + * package. Reconfigure your web.xml deployment + * descriptor to use {@link it.could.webdav.DAVServlet}. + */ +public class DAVServlet extends it.could.webdav.DAVServlet { + + /** + *

Create a new {@link DAVServlet} instance.

+ */ + public DAVServlet() { + super(); + } + + /** + *

Initialize this {@link DAVServlet} instance reporting to the + * {@link ServletContext} log that this class is deprecated.

+ */ + 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/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html b/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html new file mode 100644 index 000000000..cdc5bf1ee --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/main/java/org/betaversion/webdav/package.html @@ -0,0 +1,15 @@ + + + Could.IT WebDAV Servlet + + +

+ This package is deprecated, but preserved to maintain compatibility + with previous versions. +

+

+ Please refer to the documentation in the {@link it.could.webdav} package + for the new version description. +

+ + diff --git a/springy/archiva-web/archiva-webdav/src/main/resources/org/apache/maven/archiva/webdav/util/mime-types.txt b/springy/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/springy/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 . + +# 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/springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types b/springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/mime.types new file mode 100644 index 000000000..5baed56f7 --- /dev/null +++ b/springy/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 . + +# 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/springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props b/springy/archiva-web/archiva-webdav/src/main/resources/plexus-webdav/webdav.props new file mode 100644 index 000000000..815c14baf --- /dev/null +++ b/springy/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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java b/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/TestableHttpServletRequest.java new file mode 100644 index 000000000..f214139db --- /dev/null +++ b/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/servlet/multiplexed/MultiplexedDavServerRequestTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractBasicWebdavProviderTestCase.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractMultiWebdavProviderTestCase.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavIndexHtmlTestCase.java b/springy/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/springy/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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavProviderTestCase.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/AbstractWebdavServer.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/test/TestMultiWebDavServlet.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/MimeTypesTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/java/org/apache/maven/archiva/webdav/util/WrappedRepositoryRequestTest.java b/springy/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/springy/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 Joakim Erdfelt + * @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/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml b/springy/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/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentBasicTest.xml @@ -0,0 +1,38 @@ + + + + + + + + org.apache.maven.archiva.webdav.DavServerManager + simple + org.apache.maven.archiva.webdav.DefaultDavServerManager + DefaultDavServerManager + + + org.apache.maven.archiva.webdav.DavServerComponent + simple + + + + + + diff --git a/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml b/springy/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/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentIndexHtmlTest.xml @@ -0,0 +1,38 @@ + + + + + + + + org.apache.maven.archiva.webdav.DavServerManager + simple + org.apache.maven.archiva.webdav.DefaultDavServerManager + DefaultDavServerManager + + + org.apache.maven.archiva.webdav.DavServerComponent + simple + + + + + + diff --git a/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml b/springy/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/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleDavServerComponentMultiTest.xml @@ -0,0 +1,38 @@ + + + + + + + + org.apache.maven.archiva.webdav.DavServerManager + simple + org.apache.maven.archiva.webdav.DefaultDavServerManager + DefaultDavServerManager + + + org.apache.maven.archiva.webdav.DavServerComponent + simple + + + + + + diff --git a/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml b/springy/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/springy/archiva-web/archiva-webdav/src/test/resources/org/apache/maven/archiva/webdav/simple/SimpleWebdavServer.xml @@ -0,0 +1,38 @@ + + + + + + + + org.apache.maven.archiva.webdav.DavServerManager + simple + org.apache.maven.archiva.webdav.DefaultDavServerManager + DefaultDavServerManager + + + org.apache.maven.archiva.webdav.DavServerComponent + simple + + + + + + diff --git a/springy/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml b/springy/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..93634b40e --- /dev/null +++ b/springy/archiva-web/archiva-webdav/src/test/webapp/WEB-INF/web.xml @@ -0,0 +1,43 @@ + + + + + + Apache Archiva + + + org.springframework.web.context.ContextLoaderListener + + + + contextClass + org.codehaus.plexus.spring.PlexusWebApplicationContext + + + + contextConfigLocation + + classpath*:/META-INF/plexus/components.xml + + + + diff --git a/springy/archiva-web/pom.xml b/springy/archiva-web/pom.xml index 082817ba1..21aa1d08d 100644 --- a/springy/archiva-web/pom.xml +++ b/springy/archiva-web/pom.xml @@ -33,6 +33,7 @@ archiva-applet archiva-security archiva-webapp + archiva-webdav archiva-standalone diff --git a/springy/pom.xml b/springy/pom.xml index f4e79eed9..4bd4d7661 100644 --- a/springy/pom.xml +++ b/springy/pom.xml @@ -157,6 +157,11 @@ 1.2_Java1.3 test
+ + org.slf4j + slf4j-log4j12 + test + @@ -304,6 +309,11 @@ archiva-xml-tools 1.1-SNAPSHOT
+ + org.apache.maven.archiva + archiva-webdav + 1.1-SNAPSHOT + commons-collections commons-collections @@ -390,7 +400,7 @@ log4j log4j - 1.2.8 + 1.2.14 org.apache.lucene @@ -640,16 +650,6 @@ - - org.codehaus.plexus.webdav - plexus-webdav-simple - 1.0-beta-2 - - - org.codehaus.plexus.webdav - plexus-webdav-api - 1.0-beta-2 - javax.servlet servlet-api @@ -663,7 +663,7 @@ org.slf4j slf4j-log4j12 - 1.2 + 1.5.0 taglibs @@ -743,6 +743,7 @@ maven-javadoc-plugin + 2.2 1.5 true -- 2.39.5