[KARAF-3712] Move maven-proxy module to Cave Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/5bdbfa6b Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/5bdbfa6b Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/5bdbfa6b
Branch: refs/heads/master Commit: 5bdbfa6bf1ae7c4e95194e21f92f3a4bc7ffb582 Parents: 0deee4e Author: Guillaume Nodet <[email protected]> Authored: Tue May 5 16:51:16 2015 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Tue May 5 16:51:16 2015 +0200 ---------------------------------------------------------------------- services/maven-proxy/pom.xml | 107 --- .../services/mavenproxy/internal/Activator.java | 87 --- .../mavenproxy/internal/DefaultFuture.java | 358 --------- .../mavenproxy/internal/FutureListener.java | 35 - .../internal/InvalidMavenArtifactRequest.java | 35 - .../mavenproxy/internal/MavenProxyServlet.java | 675 ----------------- .../mavenproxy/internal/ThreadFactory.java | 43 -- .../internal/MavenProxyServletTest.java | 743 ------------------- services/pom.xml | 1 - 9 files changed, 2084 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/pom.xml ---------------------------------------------------------------------- diff --git a/services/maven-proxy/pom.xml b/services/maven-proxy/pom.xml deleted file mode 100644 index 4c171a3..0000000 --- a/services/maven-proxy/pom.xml +++ /dev/null @@ -1,107 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - <!-- - - 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. - --> - - <modelVersion>4.0.0</modelVersion> - - <parent> - <artifactId>karaf</artifactId> - <groupId>org.apache.karaf</groupId> - <version>4.0.0-SNAPSHOT</version> - <relativePath>../../pom.xml</relativePath> - </parent> - - <groupId>org.apache.karaf.services</groupId> - <artifactId>org.apache.karaf.services.mavenproxy</artifactId> - <packaging>bundle</packaging> - <name>Apache Karaf :: OSGi Services :: Maven Proxy</name> - <description>Maven Proxy Service</description> - - <build> - <plugins> - <plugin> - <groupId>org.apache.karaf.tooling</groupId> - <artifactId>karaf-services-maven-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.apache.felix</groupId> - <artifactId>maven-bundle-plugin</artifactId> - <configuration> - <instructions> - <Import-Package> - javax.servlet.*;version="[3.0,4)", - !shaded.*, - * - </Import-Package> - <Private-Package> - org.apache.karaf.services.mavenproxy.internal, - org.apache.karaf.util, - org.apache.karaf.util.base64 - </Private-Package> - </instructions> - </configuration> - </plugin> - </plugins> - </build> - - <dependencies> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>org.osgi.core</artifactId> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>${servlet.spec.groupId}</groupId> - <artifactId>${servlet.spec.artifactId}</artifactId> - </dependency> - <dependency> - <groupId>org.osgi</groupId> - <artifactId>org.osgi.compendium</artifactId> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.ops4j.pax.url</groupId> - <artifactId>pax-url-aether</artifactId> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.apache.karaf</groupId> - <artifactId>org.apache.karaf.util</artifactId> - </dependency> - - <!-- Test Dependencies --> - <dependency> - <groupId>org.ops4j.pax.web</groupId> - <artifactId>pax-web-jetty-bundle</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymock</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-log4j12</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - -</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/Activator.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/Activator.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/Activator.java deleted file mode 100644 index c1b744a..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/Activator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -import java.io.IOException; -import java.util.Enumeration; -import java.util.Hashtable; - -import org.apache.karaf.util.tracker.BaseActivator; -import org.apache.karaf.util.tracker.annotation.Managed; -import org.apache.karaf.util.tracker.annotation.RequireService; -import org.apache.karaf.util.tracker.annotation.Services; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.MavenResolvers; -import org.osgi.service.cm.ManagedService; -import org.osgi.service.http.HttpService; - -@Services( - requires = @RequireService(HttpService.class) -) -@Managed("org.apache.karaf.services.mavenproxy") -public class Activator extends BaseActivator implements ManagedService { - - private HttpService httpService; - private String alias; - private MavenResolver resolver; - private MavenProxyServlet servlet; - - @Override - protected void doStart() throws Exception { - httpService = getTrackedService(HttpService.class); - if (httpService == null) { - return; - } - - String pid = getString("maven.pid", "org.ops4j.pax.url.mvn"); - String alias = getString("maven.alias", "/mavenproxy"); - String realm = getString("maven.realm", "karaf"); - String downloadRole = getString("maven.downloadRole", null); - String uploadRole = getString("maven.uploadRole", "karaf"); - int poolSize = getInt("maven.poolSize", 8); - Hashtable<String, String> config = new Hashtable<>(); - if (getConfiguration() != null) { - for (Enumeration<String> e = getConfiguration().keys(); e.hasMoreElements();) { - String key = e.nextElement(); - String val = getConfiguration().get(key).toString(); - config.put(key, val); - } - } - this.resolver = MavenResolvers.createMavenResolver(null, config, pid); - this.alias = alias; - this.servlet = new MavenProxyServlet(this.resolver, poolSize, realm, downloadRole, uploadRole); - this.httpService.registerServlet(this.alias, this.servlet, config, null); - } - - @Override - protected void doStop() { - super.doStop(); - if (httpService != null) { - httpService.unregister(alias); - } - if (this.servlet != null) { - this.servlet.destroy(); - } - if (resolver != null) { - try { - resolver.close(); - } catch (IOException e) { - // Ignore - } - } - } -} http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/DefaultFuture.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/DefaultFuture.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/DefaultFuture.java deleted file mode 100644 index 216d644..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/DefaultFuture.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * A simple future - */ -public class DefaultFuture<T extends DefaultFuture> { - - /** - * A default value to indicate the future has been canceled - */ - private static final Object CANCELED = new Object(); - - /** - * A number of seconds to wait between two deadlock controls ( 5 seconds ) - */ - private static final long DEAD_LOCK_CHECK_INTERVAL = 5000L; - - /** - * A lock used by the wait() method - */ - private final Object lock; - private FutureListener<T> firstListener; - private List<FutureListener<T>> otherListeners; - private Object result; - private boolean ready; - private int waiters; - - - public DefaultFuture() { - this(null); - } - - /** - * Creates a new instance. - */ - public DefaultFuture(Object lock) { - this.lock = lock != null ? lock : this; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public T await() throws InterruptedException { - synchronized (lock) { - while (!ready) { - waiters++; - try { - // Wait for a notify, or if no notify is called, - // assume that we have a deadlock and exit the - // loop to check for a potential deadlock. - lock.wait(DEAD_LOCK_CHECK_INTERVAL); - } finally { - waiters--; - if (!ready) { - checkDeadLock(); - } - } - } - } - return (T) this; - } - - /** - * {@inheritDoc} - */ - public boolean await(long timeout, TimeUnit unit) throws InterruptedException { - return await(unit.toMillis(timeout)); - } - - /** - * {@inheritDoc} - */ - public boolean await(long timeoutMillis) throws InterruptedException { - return await0(timeoutMillis, true); - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public T awaitUninterruptibly() { - try { - await0(Long.MAX_VALUE, false); - } catch (InterruptedException ie) { - // Do nothing : this catch is just mandatory by contract - } - - return (T) this; - } - - /** - * {@inheritDoc} - */ - public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { - return awaitUninterruptibly(unit.toMillis(timeout)); - } - - /** - * {@inheritDoc} - */ - public boolean awaitUninterruptibly(long timeoutMillis) { - try { - return await0(timeoutMillis, false); - } catch (InterruptedException e) { - throw new InternalError(); - } - } - - /** - * Wait for the Future to be ready. If the requested delay is 0 or - * negative, this method immediately returns the value of the - * 'ready' flag. - * Every 5 second, the wait will be suspended to be able to check if - * there is a deadlock or not. - * - * @param timeoutMillis The delay we will wait for the Future to be ready - * @param interruptable Tells if the wait can be interrupted or not - * @return <code>true</code> if the Future is ready - * @throws InterruptedException If the thread has been interrupted - * when it's not allowed. - */ - private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException { - long endTime = System.currentTimeMillis() + timeoutMillis; - - synchronized (lock) { - if (ready) { - return true; - } else if (timeoutMillis <= 0) { - return false; - } - - waiters++; - try { - for (; ;) { - try { - long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL); - lock.wait(timeOut); - } catch (InterruptedException e) { - if (interruptable) { - throw e; - } - } - - if (ready) { - return true; - } else if (endTime < System.currentTimeMillis()) { - return false; - } - } - } finally { - waiters--; - if (!ready) { - checkDeadLock(); - } - } - } - } - - - /** - * TODO checkDeadLock. - */ - private void checkDeadLock() { -// // Only read / write / connect / write future can cause dead lock. -// if (!(this instanceof CloseFuture || this instanceof WriteFuture || -// this instanceof ReadFuture || this instanceof ConnectFuture)) { -// return; -// } -// -// // Get the current thread stackTrace. -// // Using Thread.currentThread().getStackTrace() is the best solution, -// // even if slightly less efficient than doing a new Exception().getStackTrace(), -// // as internally, it does exactly the same thing. The advantage of using -// // this solution is that we may benefit some improvement with some -// // future versions of Java. -// StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); -// -// // Simple and quick check. -// for (StackTraceElement s: stackTrace) { -// if (AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) { -// IllegalStateException e = new IllegalStateException( "t" ); -// e.getStackTrace(); -// throw new IllegalStateException( -// "DEAD LOCK: " + IoFuture.class.getSimpleName() + -// ".await() was invoked from an I/O processor thread. " + -// "Please use " + IoFutureListener.class.getSimpleName() + -// " or configure a proper thread model alternatively."); -// } -// } -// -// // And then more precisely. -// for (StackTraceElement s: stackTrace) { -// try { -// Class<?> cls = DefaultSshFuture.class.getClassLoader().loadClass(s.getClassName()); -// if (IoProcessor.class.isAssignableFrom(cls)) { -// throw new IllegalStateException( -// "DEAD LOCK: " + IoFuture.class.getSimpleName() + -// ".await() was invoked from an I/O processor thread. " + -// "Please use " + IoFutureListener.class.getSimpleName() + -// " or configure a proper thread model alternatively."); -// } -// } catch (Exception cnfe) { -// // Ignore -// } -// } - } - - /** - * {@inheritDoc} - */ - public boolean isDone() { - synchronized (lock) { - return ready; - } - } - - /** - * Sets the result of the asynchronous operation, and mark it as finished. - */ - public void setValue(Object newValue) { - synchronized (lock) { - // Allow only once. - if (ready) { - return; - } - - result = newValue; - ready = true; - if (waiters > 0) { - lock.notifyAll(); - } - } - - notifyListeners(); - } - - /** - * Returns the result of the asynchronous operation. - */ - protected Object getValue() { - synchronized (lock) { - return result; - } - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public T addListener(FutureListener<T> listener) { - if (listener == null) { - throw new NullPointerException("listener"); - } - - boolean notifyNow = false; - synchronized (lock) { - if (ready) { - notifyNow = true; - } else { - if (firstListener == null) { - firstListener = listener; - } else { - if (otherListeners == null) { - otherListeners = new ArrayList<>(1); - } - otherListeners.add(listener); - } - } - } - - if (notifyNow) { - notifyListener(listener); - } - return (T) this; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - public T removeListener(FutureListener<T> listener) { - if (listener == null) { - throw new NullPointerException("listener"); - } - - synchronized (lock) { - if (!ready) { - if (listener == firstListener) { - if (otherListeners != null && !otherListeners.isEmpty()) { - firstListener = otherListeners.remove(0); - } else { - firstListener = null; - } - } else if (otherListeners != null) { - otherListeners.remove(listener); - } - } - } - - return (T) this; - } - - private void notifyListeners() { - // There won't be any visibility problem or concurrent modification - // because 'ready' flag will be checked against both addListener and - // removeListener calls. - if (firstListener != null) { - notifyListener(firstListener); - firstListener = null; - - if (otherListeners != null) { - for (FutureListener<T> l : otherListeners) { - notifyListener(l); - } - otherListeners = null; - } - } - } - - @SuppressWarnings("unchecked") - private void notifyListener(FutureListener<T> l) { - try { - l.operationComplete((T) this); - } catch (Throwable t) { - // TODO - t.printStackTrace(); - } - } - - public boolean isCanceled() { - return getValue() == CANCELED; - } - - public void cancel() { - setValue(CANCELED); - } -} http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/FutureListener.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/FutureListener.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/FutureListener.java deleted file mode 100644 index 11f6c82..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/FutureListener.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -/** - * Something interested in being notified when the completion - * of an asynchronous download operation : {@link DefaultFuture}. - * - */ -public interface FutureListener<T extends DefaultFuture> { - - /** - * Invoked when the operation associated with the {@link DefaultFuture} - * has been completed even if you add the listener after the completion. - * - * @param future The source {@link DefaultFuture} which called this - * callback. - */ - void operationComplete(T future); - -} http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/InvalidMavenArtifactRequest.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/InvalidMavenArtifactRequest.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/InvalidMavenArtifactRequest.java deleted file mode 100644 index d763b61..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/InvalidMavenArtifactRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -public class InvalidMavenArtifactRequest extends Exception { - - public InvalidMavenArtifactRequest() { - } - - public InvalidMavenArtifactRequest(String s) { - super(s); - } - - public InvalidMavenArtifactRequest(String s, Throwable throwable) { - super(s, throwable); - } - - public InvalidMavenArtifactRequest(Throwable throwable) { - super(throwable); - } -} http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/MavenProxyServlet.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/MavenProxyServlet.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/MavenProxyServlet.java deleted file mode 100644 index ce7eba9..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/MavenProxyServlet.java +++ /dev/null @@ -1,675 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.security.Principal; -import java.util.Enumeration; -import java.util.Properties; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.AccountException; -import javax.security.auth.login.FailedLoginException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.AsyncContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.bind.DatatypeConverter; - -import org.apache.karaf.util.StreamUtils; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.HttpContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MavenProxyServlet extends HttpServlet { - - public static Logger LOGGER = LoggerFactory.getLogger(MavenProxyServlet.class); - - public static final Pattern REPOSITORY_ID_REGEX = Pattern.compile("[^ ]*(@id=([^@ ]+))+[^ ]*"); - - private static final String SNAPSHOT_TIMESTAMP_REGEX = "^([0-9]{8}.[0-9]{6}-[0-9]+).*"; - private static final Pattern SNAPSHOT_TIMESTAMP_PATTERN = Pattern.compile(SNAPSHOT_TIMESTAMP_REGEX); - - //The pattern below matches a path to the following: - //1: groupId - //2: artifactId - //3: version - //4: artifact filename - public static final Pattern ARTIFACT_REQUEST_URL_REGEX = Pattern.compile("([^ ]+)/([^/ ]+)/([^/ ]+)/([^/ ]+)"); - - //The pattern bellow matches the path to the following: - //1: groupId - //2: artifactId - //3: version - //4: maven-metadata xml filename - //7: repository id. - //9: type - public static final Pattern ARTIFACT_METADATA_URL_REGEX = Pattern.compile("([^ ]+)/([^/ ]+)/([^/ ]+)/(maven-metadata([-]([^ .]+))?.xml)([.]([^ ]+))?"); - - private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - private static final String HEADER_AUTHORIZATION = "Authorization"; - private static final String AUTHENTICATION_SCHEME_BASIC = "Basic"; - - protected static final String LOCATION_HEADER = "X-Location"; - - private final ConcurrentMap<String, ArtifactDownloadFuture> requestMap = new ConcurrentHashMap<>(); - private final int threadMaximumPoolSize; - private final String realm; - private final String downloadRole; - private final String uploadRole; - private ThreadPoolExecutor executorService; - - protected File tmpFolder = new File(System.getProperty("karaf.data") + File.separator + "maven" + File.separator + "proxy" + File.separator + "tmp"); - - final MavenResolver resolver; - - public MavenProxyServlet(MavenResolver resolver, int threadMaximumPoolSize, String realm, String downloadRole, String uploadRole) { - this.resolver = resolver; - this.threadMaximumPoolSize = threadMaximumPoolSize; - this.realm = realm; - this.downloadRole = downloadRole; - this.uploadRole = uploadRole; - } - - - // - // Lifecycle - // - - @Override - public void init() throws ServletException { - if (!tmpFolder.exists() && !tmpFolder.mkdirs()) { - throw new ServletException("Failed to create temporary artifact folder"); - } - // Create a thread pool with the given maxmimum number of threads - // All threads will time out after 60 seconds - int nbThreads = threadMaximumPoolSize > 0 ? threadMaximumPoolSize : 8; - executorService = new ThreadPoolExecutor(0, nbThreads, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), new ThreadFactory("MavenDownloadProxyServlet")); - } - - @Override - public void destroy() { - if (executorService != null) { - executorService.shutdown(); - try { - executorService.awaitTermination(5, TimeUnit.MINUTES); - } catch (InterruptedException e) { - executorService.shutdownNow(); - } - } - } - - - - // - // Security - // - - protected boolean authorize(HttpServletRequest request, HttpServletResponse response, String role) throws IOException { - if (role == null) { - return true; - } - // Return immediately if the header is missing - String authHeader = request.getHeader(HEADER_AUTHORIZATION); - if (authHeader != null && authHeader.length() > 0) { - - // Get the authType (Basic, Digest) and authInfo (user/password) - // from the header - authHeader = authHeader.trim(); - int blank = authHeader.indexOf(' '); - if (blank > 0) { - String authType = authHeader.substring(0, blank); - String authInfo = authHeader.substring(blank).trim(); - - // Check whether authorization type matches - if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) { - try { - String srcString = base64Decode(authInfo); - int i = srcString.indexOf(':'); - String username = srcString.substring(0, i); - String password = srcString.substring(i + 1); - - // authenticate - Subject subject = doAuthenticate(username, password, role); - if (subject != null) { - // as per the spec, set attributes - request.setAttribute(HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH); - request.setAttribute(HttpContext.REMOTE_USER, username); - // succeed - return true; - } - } catch (Exception e) { - // Ignore - } - } - } - } - - // request authentication - try { - response.setHeader(HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\""); - // must response with status and flush as Jetty may report org.eclipse.jetty.server.Response Committed before 401 null - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentLength(0); - response.flushBuffer(); - } catch (IOException ioe) { - // failed sending the response ... cannot do anything about it - } - - // inform HttpService that authentication failed - return false; - } - - private static String base64Decode(String srcString) { - byte[] transformed = DatatypeConverter.parseBase64Binary(srcString); - return new String(transformed, StandardCharsets.ISO_8859_1); - } - - public Subject doAuthenticate(final String username, final String password, final String role) { - try { - Subject subject = new Subject(); - LoginContext loginContext = new LoginContext(realm, subject, new CallbackHandler() { - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(username); - } else if (callback instanceof PasswordCallback) { - ((PasswordCallback) callback).setPassword(password.toCharArray()); - } else { - throw new UnsupportedCallbackException(callback); - } - } - } - }); - loginContext.login(); - if (role != null && role.length() > 0) { - String clazz = "org.apache.karaf.jaas.boot.principal.RolePrincipal"; - String name = role; - int idx = role.indexOf(':'); - if (idx > 0) { - clazz = role.substring(0, idx); - name = role.substring(idx + 1); - } - boolean found = false; - for (Principal p : subject.getPrincipals()) { - if (p.getClass().getName().equals(clazz) - && p.getName().equals(name)) { - found = true; - break; - } - } - if (!found) { - throw new FailedLoginException("User does not have the required role " + role); - } - } - return subject; - } catch (AccountException e) { - LOGGER.warn("Account failure", e); - return null; - } catch (LoginException e) { - LOGGER.debug("Login failed", e); - return null; - } - } - - - - - - // - // Download - // - - @Override - protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - if (!authorize(req, resp, downloadRole)) { - return; - } - String tpath = req.getPathInfo(); - if (tpath == null) { - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return; - } - if (tpath.startsWith("/")) { - tpath = tpath.substring(1); - } - final String path = tpath; - - final AsyncContext asyncContext = req.startAsync(); - asyncContext.setTimeout(TimeUnit.MINUTES.toMillis(5)); - final ArtifactDownloadFuture future = new ArtifactDownloadFuture(path); - ArtifactDownloadFuture masterFuture = requestMap.putIfAbsent(path, future); - if (masterFuture == null) { - masterFuture = future; - masterFuture.lock(); - executorService.execute(new Runnable() { - @Override - public void run() { - try { - File file = download(path); - future.setValue(file); - } catch (Throwable t) { - future.setValue(t); - } - } - }); - } else { - masterFuture.lock(); - } - masterFuture.addListener(new FutureListener<ArtifactDownloadFuture>() { - @Override - public void operationComplete(ArtifactDownloadFuture future) { - Object value = future.getValue(); - if (value instanceof Throwable) { - LOGGER.warn("Error while downloading artifact: {}", ((Throwable) value).getMessage(), value); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } else if (value instanceof File) { - File artifactFile = (File) value; - try (InputStream is = new FileInputStream(artifactFile)) { - LOGGER.info("Writing response for file : {}", path); - resp.setStatus(HttpServletResponse.SC_OK); - resp.setContentType("application/octet-stream"); - resp.setDateHeader("Date", System.currentTimeMillis()); - resp.setHeader("Connection", "close"); - resp.setContentLength(is.available()); - Bundle bundle = FrameworkUtil.getBundle(getClass()); - if (bundle != null) { - resp.setHeader("Server", bundle.getSymbolicName() + "/" + bundle.getVersion()); - } else { - resp.setHeader("Server", "Karaf Maven Proxy"); - } - StreamUtils.copy(is, resp.getOutputStream()); - } catch (Exception e) { - LOGGER.warn("Error while sending artifact: {}", e.getMessage(), e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } else { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - future.release(); - try { - asyncContext.complete(); - } catch (IllegalStateException e) { - // Ignore, the response must have already been sent with an error - } - } - }); - } - - public File download(String path) throws InvalidMavenArtifactRequest { - if (path == null) { - throw new InvalidMavenArtifactRequest(); - } - - Matcher artifactMatcher = ARTIFACT_REQUEST_URL_REGEX.matcher(path); - Matcher metadataMatcher = ARTIFACT_METADATA_URL_REGEX.matcher(path); - - if (metadataMatcher.matches()) { - LOGGER.info("Received request for maven metadata : {}", path); - try { - MavenCoord coord = convertMetadataPathToCoord(path); - return resolver.resolveMetadata(coord.groupId, coord.artifactId, coord.type, coord.version); - } catch (Exception e) { - LOGGER.warn(String.format("Could not find metadata : %s due to %s", path, e.getMessage()), e); - return null; - } - } else if (artifactMatcher.matches()) { - LOGGER.info("Received request for maven artifact : {}", path); - try { - MavenCoord artifact = convertArtifactPathToCoord(path); - Path download = resolver.resolve(artifact.groupId, artifact.artifactId, artifact.classifier, artifact.type, artifact.version).toPath(); - Path tmpFile = Files.createTempFile("mvn-", ".tmp"); - Files.copy(download, tmpFile, StandardCopyOption.REPLACE_EXISTING); - return tmpFile.toFile(); - } catch (Exception e) { - LOGGER.warn(String.format("Could not find artifact : %s due to %s", path, e.getMessage()), e); - return null; - } - } - return null; - } - - private class ArtifactDownloadFuture extends DefaultFuture<ArtifactDownloadFuture> { - - private final AtomicInteger participants = new AtomicInteger(); - private final String path; - - private ArtifactDownloadFuture(String path) { - this.path = path; - } - - public void lock() { - participants.incrementAndGet(); - } - - public void release() { - if (participants.decrementAndGet() == 0) { - requestMap.remove(path); - Object v = getValue(); - if (v instanceof File) { - ((File) v).delete(); - } - } - } - - } - - - - // - // Upload - // - - @Override - protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (!authorize(request, response, uploadRole)) { - return; - } - try { - String path = request.getPathInfo(); - //Make sure path is valid - if (path != null) { - if (path.startsWith("/")) { - path = path.substring(1); - } - } - if (path == null || path.isEmpty()) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - boolean result; - // handle move - String location = request.getHeader(LOCATION_HEADER); - if (location != null) { - result = upload(new File(location), path, response); - } else { - Path dir = tmpFolder.toPath().resolve(UUID.randomUUID().toString()); - Path temp = dir.resolve(Paths.get(path).getFileName()); - Files.createDirectories(dir); - try (OutputStream os = Files.newOutputStream(temp)) { - StreamUtils.copy(request.getInputStream(), os); - } - result = upload(temp.toFile(), path, response); - } - - response.setStatus(result ? HttpServletResponse.SC_ACCEPTED : HttpServletResponse.SC_NOT_ACCEPTABLE); - - } catch (InvalidMavenArtifactRequest ex) { - // must response with status and flush as Jetty may report org.eclipse.jetty.server.Response Committed before 401 null - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.setContentLength(0); - response.flushBuffer(); - } catch (Exception ex) { - // must response with status and flush as Jetty may report org.eclipse.jetty.server.Response Committed before 401 null - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.setContentLength(0); - response.flushBuffer(); - } - - } - - protected boolean upload(File input, String path, HttpServletResponse response) throws InvalidMavenArtifactRequest, NoSuchFileException { - if (!input.isFile()) { - throw new NoSuchFileException(input.toString()); - } - if (path == null) { - throw new InvalidMavenArtifactRequest(); - } - // root path, try reading mvn coords - if (path.indexOf('/') < 0) { - try { - String mvnCoordsPath = readMvnCoordsPath(input); - if (mvnCoordsPath != null) { - return install(input, mvnCoordsPath); - } else { - response.addHeader(LOCATION_HEADER, input.toString()); // we need manual mvn coords input - return true; - } - } catch (Exception e) { - LOGGER.warn(String.format("Failed to deploy artifact : %s due to %s", path, e.getMessage()), e); - return false; - } - } - - return install(input, path); - - } - - private boolean install(File file, String path) { - Matcher artifactMatcher = ARTIFACT_REQUEST_URL_REGEX.matcher(path); - Matcher metadataMatcher = ARTIFACT_METADATA_URL_REGEX.matcher(path); - - if (metadataMatcher.matches()) { - LOGGER.info("Received upload request for maven metadata : {}", path); - try { - MavenCoord coord = convertMetadataPathToCoord(path); - resolver.uploadMetadata(coord.groupId, coord.artifactId, coord.type, coord.version, file); - LOGGER.info("Maven metadata installed: {}", coord.toString()); - } catch (Exception e) { - LOGGER.warn(String.format("Failed to upload metadata: %s due to %s", path, e.getMessage()), e); - return false; - } - //If no matching metadata found return nothing - } else if (artifactMatcher.matches()) { - LOGGER.info("Received upload request for maven artifact : {}", path); - try { - MavenCoord coord = convertArtifactPathToCoord(path); - resolver.upload(coord.groupId, coord.artifactId, coord.classifier, coord.type, coord.version, file); - LOGGER.info("Artifact installed: {}", coord.toString()); - } catch (Exception e) { - LOGGER.warn(String.format("Failed to upload artifact : %s due to %s", path, e.getMessage()), e); - return false; - } - } - return false; - } - - protected static String readMvnCoordsPath(File file) throws Exception { - try (JarFile jarFile = new JarFile(file)) { - String previous = null; - String match = null; - - Enumeration<JarEntry> entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.startsWith("META-INF/maven/") && name.endsWith("pom.properties")) { - if (previous != null) { - throw new IllegalStateException(String.format("Duplicate pom.properties found: %s != %s", name, previous)); - } - - previous = name; // check for dups - - Properties props = new Properties(); - try (InputStream stream = jarFile.getInputStream(entry)) { - props.load(stream); - } - String groupId = props.getProperty("groupId"); - String artifactId = props.getProperty("artifactId"); - String version = props.getProperty("version"); - String type = getFileExtension(file); - match = String.format("%s/%s/%s/%s-%s.%s", groupId, artifactId, version, artifactId, version, type != null ? type : "jar"); - } - } - - return match; - } - } - - private static String getFileExtension(File file) { - String fileName = file.getName(); - int idx = fileName.lastIndexOf('.'); - if (idx > 1) { - String answer = fileName.substring(idx + 1); - if (answer.length() > 0) { - return answer; - } - } - return null; - } - - /** - * Converts the path of the request to maven coords. - * - * @param path The request path, following the format: {@code <groupId>/<artifactId>/<version>/<artifactId>-<version>-[<classifier>].extension} - * @return A {@link MavenCoord} - * @throws InvalidMavenArtifactRequest - */ - protected MavenCoord convertArtifactPathToCoord(String path) throws InvalidMavenArtifactRequest { - if (path == null) { - throw new InvalidMavenArtifactRequest("Cannot match request path to maven url, request path is empty."); - } - Matcher pathMatcher = ARTIFACT_REQUEST_URL_REGEX.matcher(path); - if (pathMatcher.matches()) { - String groupId = pathMatcher.group(1).replaceAll("/", "."); - String artifactId = pathMatcher.group(2); - String version = pathMatcher.group(3); - String filename = pathMatcher.group(4); - String extension; - String classifier = ""; - String filePerfix = artifactId + "-" + version; - String stripedFileName; - - if (version.endsWith("SNAPSHOT")) { - String baseVersion = version.replaceAll("-SNAPSHOT", ""); - String timestampedFileName = filename.substring(artifactId.length() + baseVersion.length() + 2); - //Check if snapshot is timestamped and override the version. @{link Artifact} will still treat it as a SNAPSHOT. - //and also in case of artifact installation the proper filename will be used. - Matcher ts = SNAPSHOT_TIMESTAMP_PATTERN.matcher(timestampedFileName); - if (ts.matches()) { - version = baseVersion + "-" + ts.group(1); - filePerfix = artifactId + "-" + version; - } - stripedFileName = filename.replaceAll(SNAPSHOT_TIMESTAMP_REGEX, "SNAPSHOT"); - stripedFileName = stripedFileName.substring(filePerfix.length()); - } else { - stripedFileName = filename.substring(filePerfix.length()); - } - - if (stripedFileName.startsWith("-") && stripedFileName.contains(".")) { - classifier = stripedFileName.substring(1, stripedFileName.indexOf('.')); - } - extension = stripedFileName.substring(stripedFileName.indexOf('.') + 1); - - MavenCoord coord = new MavenCoord(); - coord.groupId = groupId; - coord.artifactId = artifactId; - coord.type = extension; - coord.classifier = classifier; - coord.version = version; - return coord; - } - return null; - } - - /** - * Converts the path of the request to {@link MavenCoord}. - * - * @param path The request path, following the format: {@code <groupId>/<artifactId>/<version>/<artifactId>-<version>-[<classifier>].extension} - * @return A {@link MavenCoord} - * @throws InvalidMavenArtifactRequest - */ - protected MavenCoord convertMetadataPathToCoord(String path) throws InvalidMavenArtifactRequest { - if (path == null) { - throw new InvalidMavenArtifactRequest("Cannot match request path to maven url, request path is empty."); - } - Matcher pathMatcher = ARTIFACT_METADATA_URL_REGEX.matcher(path); - if (pathMatcher.matches()) { - MavenCoord coord = new MavenCoord(); - coord.groupId = pathMatcher.group(1).replaceAll("/", "."); - coord.artifactId = pathMatcher.group(2); - coord.version = pathMatcher.group(3); - String type = pathMatcher.group(8); - coord.type = type == null ? "maven-metadata.xml" : "maven-metadata.xml." + type; - return coord; - } - return null; - } - - static class MavenCoord { - String groupId; - String artifactId; - String type; - String classifier; - String version; - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(groupId).append(":").append(artifactId).append(":"); - sb.append(type).append(":"); - if (classifier != null && !classifier.isEmpty()) { - sb.append(classifier).append(":"); - } - sb.append(version); - return sb.toString(); - } - } - - /** - * Reads a {@link java.io.File} from the {@link java.io.InputStream} then saves it under a temp location and returns the file. - * - * @param is The source input stream. - * @param tempLocation The temporary location to save the content of the stream. - * @param name The name of the file. - * @return - * @throws java.io.FileNotFoundException - */ - protected File copyFile(InputStream is, File tempLocation, String name) throws IOException { - Path tmpFile = tempLocation.toPath().resolve(name); - Files.deleteIfExists(tmpFile); - try (OutputStream os = Files.newOutputStream(tmpFile)) { - StreamUtils.copy(is, os); - } - return tmpFile.toFile(); - } - -} http://git-wip-us.apache.org/repos/asf/karaf/blob/5bdbfa6b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/ThreadFactory.java ---------------------------------------------------------------------- diff --git a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/ThreadFactory.java b/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/ThreadFactory.java deleted file mode 100644 index de3c737..0000000 --- a/services/maven-proxy/src/main/java/org/apache/karaf/services/mavenproxy/internal/ThreadFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.karaf.services.mavenproxy.internal; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A {@link ThreadFactory} which sets the thread name to an unique name. - * <p/> - * The thread name uses the following syntax <tt>name #counter</tt>, where counter in an unique counter, starting from 1. - */ -public final class ThreadFactory implements java.util.concurrent.ThreadFactory { - - private static final AtomicInteger counter = new AtomicInteger(); - - private final String name; - - /** - * Prefix of the thread name - */ - public ThreadFactory(final String name) { - this.name = name; - } - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, name + " #" + counter.incrementAndGet()); - } -}
