[KARAF-3404] Add a notion of profiles to karaf [KARAF-3406] Upgrade to pax-url 2.2 [KARAF-3407] Upgrade to felix utils 1.6.2
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/90cb480d Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/90cb480d Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/90cb480d Branch: refs/heads/master Commit: 90cb480dc0d941fc39af220938bfacb979c8b7ff Parents: b10a009 Author: Guillaume Nodet <[email protected]> Authored: Wed Dec 10 23:55:49 2014 +0100 Committer: Guillaume Nodet <[email protected]> Committed: Thu Dec 11 00:02:07 2014 +0100 ---------------------------------------------------------------------- .../standard/src/main/feature/feature.xml | 7 + features/core/pom.xml | 10 + .../internal/download/DownloadManagers.java | 35 + .../internal/download/StreamProvider.java | 7 +- .../download/impl/AbstractDownloadTask.java | 71 ++ .../impl/AbstractRetryableDownloadTask.java | 60 ++ .../internal/download/impl/DefaultFuture.java | 358 +++++++ .../download/impl/DownloadManagerHelper.java | 67 ++ .../internal/download/impl/FutureListener.java | 35 + .../download/impl/MavenDownloadManager.java | 208 +++++ .../download/impl/MavenDownloadTask.java | 37 + .../download/impl/SimpleDownloadTask.java | 117 +++ .../download/simple/SimpleDownloader.java | 127 --- .../karaf/features/internal/osgi/Activator.java | 2 +- .../features/internal/region/Subsystem.java | 33 +- .../internal/service/FeaturesServiceImpl.java | 71 +- .../internal/support/TestDownloadManager.java | 61 +- instance/pom.xml | 6 + .../karaf/instance/command/CreateCommand.java | 8 +- .../karaf/instance/core/InstanceSettings.java | 16 +- .../core/internal/InstanceServiceImpl.java | 95 +- pom.xml | 16 +- profile/pom.xml | 150 +++ .../org/apache/karaf/profile/LockHandle.java | 29 + .../karaf/profile/PlaceholderResolver.java | 40 + .../java/org/apache/karaf/profile/Profile.java | 130 +++ .../apache/karaf/profile/ProfileBuilder.java | 109 +++ .../apache/karaf/profile/ProfileService.java | 132 +++ .../profile/command/ProfileChangeParents.java | 56 ++ .../karaf/profile/command/ProfileCopy.java | 57 ++ .../karaf/profile/command/ProfileCreate.java | 53 ++ .../karaf/profile/command/ProfileDelete.java | 46 + .../karaf/profile/command/ProfileDisplay.java | 175 ++++ .../karaf/profile/command/ProfileEdit.java | 611 ++++++++++++ .../karaf/profile/command/ProfileList.java | 63 ++ .../karaf/profile/command/ProfileRename.java | 62 ++ .../command/completers/ProfileCompleter.java | 45 + .../profile/impl/PlaceholderResolvers.java | 103 ++ .../karaf/profile/impl/ProfileBuilderImpl.java | 305 ++++++ .../apache/karaf/profile/impl/ProfileImpl.java | 246 +++++ .../karaf/profile/impl/ProfileServiceImpl.java | 246 +++++ .../org/apache/karaf/profile/impl/Profiles.java | 411 ++++++++ .../org/apache/karaf/profile/impl/Utils.java | 103 ++ .../karaf/profile/impl/osgi/Activator.java | 44 + .../apache/karaf/profile/impl/ProfilesTest.java | 90 ++ tooling/karaf-maven-plugin/pom.xml | 4 + .../karaf/tooling/features/InstallKarsMojo.java | 933 +++++++++++-------- .../features/VerifyFeatureResolutionMojo.java | 60 +- .../CustomBundleURLStreamHandlerFactory.java | 60 +- .../tooling/utils/InternalMavenResolver.java | 70 ++ .../org/apache/karaf/tooling/utils/IoUtils.java | 38 + .../tracker/GenerateServiceMetadata.java | 2 - .../karaf/util/tracker/BaseActivator.java | 3 + 53 files changed, 5326 insertions(+), 597 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/assemblies/features/standard/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml index c94f496..f05172f 100644 --- a/assemblies/features/standard/src/main/feature/feature.xml +++ b/assemblies/features/standard/src/main/feature/feature.xml @@ -546,4 +546,11 @@ <bundle start="true" start-level="5">mvn:org.ops4j.pax.url/pax-url-wrap/${pax.url.version}/jar/uber</bundle> </feature> + <feature name="profile" description="Profiles support" version="${project.version}"> + <config name="org.apache.karaf.profile"> + profilesDirectory = ${karaf.home}/profiles + </config> + <bundle>mvn:org.apache.karaf.profile/org.apache.karaf.profile.core/${project.version}</bundle> + </feature> + </features> http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/pom.xml ---------------------------------------------------------------------- diff --git a/features/core/pom.xml b/features/core/pom.xml index 2a78de9..fc6ccc9 100644 --- a/features/core/pom.xml +++ b/features/core/pom.xml @@ -61,6 +61,11 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jcl-over-slf4j</artifactId> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.apache.karaf</groupId> <artifactId>org.apache.karaf.util</artifactId> <scope>provided</scope> @@ -78,6 +83,11 @@ </dependency> <dependency> + <groupId>org.ops4j.pax.url</groupId> + <artifactId>pax-url-aether</artifactId> + </dependency> + + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java new file mode 100644 index 0000000..3808bd3 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java @@ -0,0 +1,35 @@ +/* + * 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.features.internal.download; + +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.karaf.features.internal.download.impl.MavenDownloadManager; +import org.ops4j.pax.url.mvn.MavenResolver; + +public final class DownloadManagers { + + private DownloadManagers() { } + + /** + * Creates a DownloadManager + */ + public static DownloadManager createDownloadManager(MavenResolver resolver, ScheduledExecutorService executorService) { + return new MavenDownloadManager(resolver, executorService); + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java index c25fa43..9f1f686 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java @@ -16,14 +16,17 @@ */ package org.apache.karaf.features.internal.download; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Map; public interface StreamProvider { - InputStream open() throws IOException; + String getUrl(); + + File getFile() throws IOException; - Map<String, String> getMetadata() throws IOException; + InputStream open() throws IOException; } http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java new file mode 100644 index 0000000..da00bdf --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractDownloadTask.java @@ -0,0 +1,71 @@ +/* + * 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.features.internal.download.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.karaf.features.internal.download.StreamProvider; + +public abstract class AbstractDownloadTask extends DefaultFuture<AbstractDownloadTask> implements Runnable, StreamProvider { + + protected final String url; + protected ScheduledExecutorService executorService; + + public AbstractDownloadTask(ScheduledExecutorService executorService, String url) { + this.executorService = executorService; + this.url = url; + } + + public String getUrl() { + return url; + } + + public File getFile() throws IOException { + Object v = getValue(); + if (v instanceof File) { + return (File) v; + } else if (v instanceof IOException) { + throw (IOException) v; + } else { + return null; + } + } + + @Override + public InputStream open() throws IOException { + return new FileInputStream(getFile()); + } + + public void setFile(File file) { + if (file == null) { + throw new NullPointerException("file"); + } + setValue(file); + } + + public void setException(IOException exception) { + if (exception == null) { + throw new NullPointerException("exception"); + } + setValue(exception); + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java new file mode 100644 index 0000000..f315e4b --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/AbstractRetryableDownloadTask.java @@ -0,0 +1,60 @@ +/* + * 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.features.internal.download.impl; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractRetryableDownloadTask extends AbstractDownloadTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRetryableDownloadTask.class); + + private long scheduleDelay = 250; + private int scheduleNbRun = 0; + + public AbstractRetryableDownloadTask(ScheduledExecutorService executorService, String url) { + super(executorService, url); + } + + public void run() { + try { + try { + File file = download(); + setFile(file); + } catch (IOException e) { + if (++scheduleNbRun < 5) { + long delay = (long)(scheduleDelay * 3 / 2 + Math.random() * scheduleDelay / 2); + LOGGER.debug("Error downloading " + url + ": " + e.getMessage() + ". Retrying in approx " + delay + " ms."); + executorService.schedule(this, scheduleDelay, TimeUnit.MILLISECONDS); + scheduleDelay *= 2; + } else { + setException(new IOException("Error downloading " + url, e)); + } + } + } catch (Throwable e) { + setException(new IOException("Error downloading " + url, e)); + } + } + + protected abstract File download() throws Exception; + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java new file mode 100644 index 0000000..3460496 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DefaultFuture.java @@ -0,0 +1,358 @@ +/* + * 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.features.internal.download.impl; + +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/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.java new file mode 100644 index 0000000..531625f --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/DownloadManagerHelper.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.karaf.features.internal.download.impl; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class DownloadManagerHelper { + + private static final Pattern IGNORED_PROTOCOL_PATTERN = Pattern.compile("^(jar|war|war-i|warref|webbundle|wrap|spring|blueprint):.*$"); + + private DownloadManagerHelper() { + //Utility Class + } + + /** + * Strips download urls from wrapper protocols. + */ + public static String stripUrl(String url) { + String strippedUrl = url; + Matcher matcher = IGNORED_PROTOCOL_PATTERN.matcher(strippedUrl); + while (matcher.matches()) { + String protocol = matcher.group(1); + strippedUrl = strippedUrl.substring(protocol.length() + 1); + matcher = IGNORED_PROTOCOL_PATTERN.matcher(strippedUrl); + } + if (strippedUrl.contains("?")) { + strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('?')); + } + if (strippedUrl.contains("$")) { + strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('$')); + } + if (strippedUrl.contains("#")) { + strippedUrl = strippedUrl.substring(0, strippedUrl.indexOf('#')); + } + + return strippedUrl; + } + + public static String stripInlinedMavenRepositoryUrl(String url) { + if (url.startsWith("mvn:") && url.contains("!")) { + return url.substring(4, url.indexOf('!')); + } + return null; + } + + public static String removeInlinedMavenRepositoryUrl(String url) { + if (url.startsWith("mvn:") && url.contains("!")) { + return "mvn:" + url.substring(url.indexOf('!') + 1); + } + return url; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java new file mode 100644 index 0000000..1a7ae99 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/FutureListener.java @@ -0,0 +1,35 @@ +/* + * 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.features.internal.download.impl; + +/** + * 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/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java new file mode 100644 index 0000000..82f3eea --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java @@ -0,0 +1,208 @@ +/* + * 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.features.internal.download.impl; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.karaf.features.internal.download.DownloadCallback; +import org.apache.karaf.features.internal.download.DownloadManager; +import org.apache.karaf.features.internal.download.Downloader; +import org.apache.karaf.features.internal.download.StreamProvider; +import org.apache.karaf.features.internal.util.MultiException; +import org.ops4j.pax.url.mvn.MavenResolver; + +public class MavenDownloadManager implements DownloadManager { + + private final MavenResolver mavenResolver; + + private final ScheduledExecutorService executorService; + + private File tmpPath; + + private final Map<String, AbstractDownloadTask> downloaded = new HashMap<>(); + + private final Map<String, AbstractDownloadTask> downloading = new HashMap<>(); + + private final Object lock = new Object(); + + private volatile int allPending = 0; + + public MavenDownloadManager(MavenResolver mavenResolver, ScheduledExecutorService executorService) { + this.mavenResolver = mavenResolver; + this.executorService = executorService; + + String karafRoot = System.getProperty("karaf.home", "karaf"); + String karafData = System.getProperty("karaf.data", karafRoot + "/data"); + this.tmpPath = new File(karafData, "tmp"); + } + + public int getPending() { + return allPending; + } + + @Override + public Downloader createDownloader() { + return new MavenDownloader(); + } + + @SuppressWarnings("unchecked") + @Override + public Map<String, StreamProvider> getProviders() { + return (Map) Collections.synchronizedMap(downloaded); + } + + class MavenDownloader implements Downloader { + + private volatile int pending = 0; + private final MultiException exception = new MultiException("Error"); + + public int pending() { + return pending; + } + + @Override + public void await() throws InterruptedException, MultiException { + synchronized (lock) { + while (pending != 0) { + lock.wait(); + } + } + exception.throwIfExceptions(); + } + + @Override + public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException { + AbstractDownloadTask task; + synchronized (lock) { + task = downloaded.get(location); + if (task == null) { + task = downloading.get(location); + } + } + if (task == null) { + task = createDownloadTask(location); + } + synchronized (lock) { + AbstractDownloadTask prev = downloaded.get(location); + if (prev == null) { + prev = downloading.get(location); + } + if (prev == null) { + downloading.put(location, task); + executorService.execute(task); + } else { + task = prev; + } + pending++; + allPending++; + } + final AbstractDownloadTask downloadTask = task; + task.addListener(new FutureListener<AbstractDownloadTask>() { + @Override + public void operationComplete(AbstractDownloadTask future) { + try { + // Call the callback + if (downloadCallback != null) { + downloadCallback.downloaded(downloadTask); + } + // Make sure we log any download error if the callback suppressed it + downloadTask.getFile(); + } catch (Throwable e) { + exception.addSuppressed(e); + } finally { + synchronized (lock) { + downloading.remove(location); + downloaded.put(location, downloadTask); + --allPending; + if (--pending == 0) { + lock.notifyAll(); + } + } + } + } + }); + } + + private AbstractDownloadTask createDownloadTask(final String url) { + final String mvnUrl = DownloadManagerHelper.stripUrl(url); + if (mvnUrl.startsWith("mvn:")) { + if (!mvnUrl.equals(url)) { + return new ChainedDownloadTask(executorService, url, mvnUrl); + } else { + return new MavenDownloadTask(executorService, mavenResolver, mvnUrl); + } + } else { + return new SimpleDownloadTask(executorService, url, tmpPath); + } + } + + class ChainedDownloadTask extends AbstractDownloadTask { + + private String innerUrl; + + public ChainedDownloadTask(ScheduledExecutorService executorService, String url, String innerUrl) { + super(executorService, url); + this.innerUrl = innerUrl; + } + + @Override + public void run() { + try { + MavenDownloader.this.download(innerUrl, new DownloadCallback() { + @Override + public void downloaded(StreamProvider provider) throws Exception { + try { + AbstractDownloadTask future = (AbstractDownloadTask) provider; + String file = future.getFile().toURI().toURL().toExternalForm(); + String real = url.replace(innerUrl, file); + MavenDownloader.this.download(real, new DownloadCallback() { + @Override + public void downloaded(StreamProvider provider) throws Exception { + try { + setFile(provider.getFile()); + } catch (IOException e) { + setException(e); + } catch (Throwable t) { + setException(new IOException(t)); + } + } + }); + } catch (IOException e) { + setException(e); + } catch (Throwable t) { + setException(new IOException(t)); + } + } + }); + } catch (IOException e) { + setException(e); + } catch (Throwable t) { + setException(new IOException(t)); + } + } + + } + + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java new file mode 100644 index 0000000..0959e77 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java @@ -0,0 +1,37 @@ +/* + * 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.features.internal.download.impl; + +import java.io.File; +import java.util.concurrent.ScheduledExecutorService; + +import org.ops4j.pax.url.mvn.MavenResolver; + +public class MavenDownloadTask extends AbstractRetryableDownloadTask { + + private final MavenResolver resolver; + + public MavenDownloadTask(ScheduledExecutorService executor, MavenResolver resolver, String url) { + super(executor, url); + this.resolver = resolver; + } + + protected File download() throws Exception { + return resolver.resolve(url); + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java new file mode 100644 index 0000000..0767f91 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/SimpleDownloadTask.java @@ -0,0 +1,117 @@ +/* + * 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.features.internal.download.impl; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.karaf.util.StreamUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimpleDownloadTask extends AbstractRetryableDownloadTask { + + private static final String BLUEPRINT_PREFIX = "blueprint:"; + private static final String SPRING_PREFIX = "spring:"; + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(AbstractDownloadTask.class); + + private File basePath; + + public SimpleDownloadTask(ScheduledExecutorService executorService, String url, File basePath) { + super(executorService, url); + this.basePath = basePath; + } + + @Override + protected File download() throws Exception { + LOG.trace("Downloading [" + url + "]"); + + if (url.startsWith(BLUEPRINT_PREFIX) || url.startsWith(SPRING_PREFIX)) { + return downloadBlueprintOrSpring(); + } + + try { + basePath.mkdirs(); + if (!basePath.isDirectory()) { + throw new IOException("Unable to create directory " + basePath.toString()); + } + + URL urlObj = new URL(url); + File file = new File(basePath, getFileName(urlObj.getFile())); + if (file.exists()) { + return file; + } + + File dir = new File(System.getProperty("karaf.data"), "tmp"); + dir.mkdirs(); + if (!dir.isDirectory()) { + throw new IOException("Unable to create directory " + dir.toString()); + } + + File tmpFile = File.createTempFile("download-", null, dir); + + try (InputStream is = urlObj.openStream(); + OutputStream os = new FileOutputStream(tmpFile)) { + StreamUtils.copy(is, os); + } + + if (file.exists() && !file.delete()) { + throw new IOException("Unable to delete file: " + file.toString()); + } + // check: this will move the file to CHILD_HOME root directory... + if (!tmpFile.renameTo(file)) { + throw new IOException("Unable to rename file " + tmpFile.toString() + " to " + file.toString()); + } + return file; + } catch (IOException ignore) { + throw new IOException("Could not download [" + this.url + "]", ignore); + } + } + + // we only want the filename itself, not the whole path + private String getFileName(String url) { + // ENTESB-1394: we do not want all these decorators from wrap: protocol + // or any inlined maven repos + url = DownloadManagerHelper.stripUrl(url); + url = DownloadManagerHelper.removeInlinedMavenRepositoryUrl(url); + int unixPos = url.lastIndexOf('/'); + int windowsPos = url.lastIndexOf('\\'); + return url.substring(Math.max(unixPos, windowsPos) + 1); + } + + protected File downloadBlueprintOrSpring() throws Exception { + // when downloading an embedded blueprint or spring xml file, then it must be as a temporary file + File dir = new File(System.getProperty("karaf.data"), "tmp"); + dir.mkdirs(); + File tmpFile = File.createTempFile("download-", null, dir); + try (InputStream is = new URL(url).openStream(); + OutputStream os = new FileOutputStream(tmpFile)) + { + StreamUtils.copy(is, os); + } + return tmpFile; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java deleted file mode 100644 index 670b63e..0000000 --- a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java +++ /dev/null @@ -1,127 +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.features.internal.download.simple; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import org.apache.karaf.features.internal.download.DownloadCallback; -import org.apache.karaf.features.internal.download.DownloadManager; -import org.apache.karaf.features.internal.download.Downloader; -import org.apache.karaf.features.internal.download.StreamProvider; -import org.apache.karaf.features.internal.util.MultiException; - -import static java.util.jar.JarFile.MANIFEST_NAME; - -public class SimpleDownloader implements DownloadManager, Downloader { - - protected final MultiException exception = new MultiException("Error"); - - protected final ConcurrentMap<String, StreamProvider> providers = new ConcurrentHashMap<String, StreamProvider>(); - - @Override - public Downloader createDownloader() { - return this; - } - - @Override - public void await() throws InterruptedException, MultiException { - exception.throwIfExceptions(); - } - - @Override - public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException { - if (!providers.containsKey(location)) { - providers.putIfAbsent(location, createProvider(location)); - } - try { - if (downloadCallback != null) { - downloadCallback.downloaded(providers.get(location)); - } - } catch (Exception e) { - exception.addSuppressed(e); - } - } - - protected StreamProvider createProvider(String location) throws MalformedURLException { - try { - return new UrlProvider(new URL(location)); - } catch (MalformedURLException e) { - throw new IllegalStateException("Error opening URL " + location, e); - } - } - - public Map<String, StreamProvider> getProviders() { - return providers; - } - - static class UrlProvider implements StreamProvider { - private final URL url; - private volatile Map<String, String> metadata; - - UrlProvider(URL url) { - this.url = url; - } - - @Override - public InputStream open() throws IOException { - return url.openStream(); - } - - @Override - public Map<String, String> getMetadata() throws IOException { - if (metadata == null) { - synchronized (this) { - if (metadata == null) { - metadata = doGetMetadata(); - } - } - } - return metadata; - } - - protected Map<String, String> doGetMetadata() throws IOException { - try ( - InputStream is = open() - ) { - ZipInputStream zis = new ZipInputStream(is); - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - if (MANIFEST_NAME.equals(entry.getName())) { - Attributes attributes = new Manifest(zis).getMainAttributes(); - Map<String, String> headers = new HashMap<String, String>(); - for (Map.Entry attr : attributes.entrySet()) { - headers.put(attr.getKey().toString(), attr.getValue().toString()); - } - return headers; - } - } - } - throw new IllegalArgumentException("Resource " + url + " does not contain a manifest"); - } - } -} http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java index 3be20e5..9a89494 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java @@ -204,7 +204,7 @@ public class Activator extends BaseActivator { stateStorage, featureFinder, eventAdminListener, - configInstaller, + configurationAdmin, digraph, overrides, featureResolutionRange, http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java index c7cc22d..a958e8a 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java @@ -16,6 +16,8 @@ */ package org.apache.karaf.features.internal.region; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -25,6 +27,10 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.apache.felix.resolver.Util; import org.apache.felix.utils.manifest.Clause; @@ -50,6 +56,7 @@ import org.osgi.framework.Version; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; +import static java.util.jar.JarFile.MANIFEST_NAME; import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEATURE; import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM; import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement; @@ -342,7 +349,7 @@ public class Subsystem extends ResourceImpl { downloader.download(loc, new DownloadCallback() { @Override public void downloaded(StreamProvider provider) throws Exception { - ResourceImpl res = createResource(loc, provider.getMetadata()); + ResourceImpl res = createResource(loc, getMetadata(provider)); bundles.put(loc, res); } }); @@ -352,7 +359,7 @@ public class Subsystem extends ResourceImpl { downloader.download(loc, new DownloadCallback() { @Override public void downloaded(StreamProvider provider) throws Exception { - ResourceImpl res = createResource(loc, provider.getMetadata()); + ResourceImpl res = createResource(loc, getMetadata(provider)); bundles.put(loc, res); } }); @@ -362,7 +369,7 @@ public class Subsystem extends ResourceImpl { downloader.download(loc, new DownloadCallback() { @Override public void downloaded(StreamProvider provider) throws Exception { - ResourceImpl res = createResource(loc, provider.getMetadata()); + ResourceImpl res = createResource(loc, getMetadata(provider)); bundles.put(loc, res); } }); @@ -423,6 +430,26 @@ public class Subsystem extends ResourceImpl { } } + Map<String, String> getMetadata(StreamProvider provider) throws IOException { + try ( + InputStream is = provider.open(); + ) { + ZipInputStream zis = new ZipInputStream(is); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (MANIFEST_NAME.equals(entry.getName())) { + Attributes attributes = new Manifest(zis).getMainAttributes(); + Map<java.lang.String, java.lang.String> headers = new HashMap<java.lang.String, java.lang.String>(); + for (Map.Entry attr : attributes.entrySet()) { + headers.put(attr.getKey().toString(), attr.getValue().toString()); + } + return headers; + } + } + } + throw new IllegalArgumentException("Resource " + provider.getUrl() + " does not contain a manifest"); + } + void addDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) { if (isAcceptDependencies()) { doAddDependency(resource, mandatory, start, startLevel); http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java index af84db2..d357f0f 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java @@ -25,9 +25,12 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Dictionary; import java.util.EnumSet; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; +import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -39,6 +42,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import org.apache.felix.utils.version.VersionCleaner; import org.apache.felix.utils.version.VersionRange; @@ -49,13 +53,16 @@ import org.apache.karaf.features.FeaturesListener; import org.apache.karaf.features.FeaturesService; import org.apache.karaf.features.Repository; import org.apache.karaf.features.RepositoryEvent; -import org.apache.karaf.features.internal.download.simple.SimpleDownloader; +import org.apache.karaf.features.internal.download.DownloadManager; +import org.apache.karaf.features.internal.download.DownloadManagers; import org.apache.karaf.features.internal.util.JsonReader; import org.apache.karaf.features.internal.util.JsonWriter; import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionDigraph; import org.eclipse.equinox.region.RegionFilterBuilder; +import org.ops4j.pax.url.mvn.MavenResolver; +import org.ops4j.pax.url.mvn.MavenResolvers; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; @@ -66,6 +73,8 @@ import org.osgi.framework.Version; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,6 +110,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall private final StateStorage storage; private final FeatureFinder featureFinder; private final EventAdminListener eventAdminListener; + private final ConfigurationAdmin configurationAdmin; private final FeatureConfigInstaller configInstaller; private final RegionDigraph digraph; private final String overrides; @@ -143,7 +153,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall StateStorage storage, FeatureFinder featureFinder, EventAdminListener eventAdminListener, - FeatureConfigInstaller configInstaller, + ConfigurationAdmin configurationAdmin, RegionDigraph digraph, String overrides, String featureResolutionRange, @@ -155,7 +165,8 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall this.storage = storage; this.featureFinder = featureFinder; this.eventAdminListener = eventAdminListener; - this.configInstaller = configInstaller; + this.configurationAdmin = configurationAdmin; + this.configInstaller = configurationAdmin != null ? new FeatureConfigInstaller(configurationAdmin) : null; this.digraph = digraph; this.overrides = overrides; this.featureResolutionRange = featureResolutionRange; @@ -944,22 +955,50 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall EnumSet<Option> options // installation options ) throws Exception { - Set<String> prereqs = new HashSet<>(); - while (true) { - try { - Deployer.DeploymentState dstate = getDeploymentState(state); - Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options); - new Deployer(new SimpleDownloader(), this).deploy(dstate, request); - break; - } catch (Deployer.PartialDeploymentException e) { - if (!prereqs.containsAll(e.getMissing())) { - prereqs.addAll(e.getMissing()); - state = copyState(); - } else { - throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing()); + Dictionary<String, String> props = getMavenConfig(); + MavenResolver resolver = MavenResolvers.createMavenResolver(props, "org.ops4j.pax.url.mvn"); + ScheduledExecutorService executor = Executors.newScheduledThreadPool(8); + DownloadManager manager = DownloadManagers.createDownloadManager(resolver, executor); + try { + Set<String> prereqs = new HashSet<>(); + while (true) { + try { + Deployer.DeploymentState dstate = getDeploymentState(state); + Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options); + new Deployer(manager, this).deploy(dstate, request); + break; + } catch (Deployer.PartialDeploymentException e) { + if (!prereqs.containsAll(e.getMissing())) { + prereqs.addAll(e.getMissing()); + state = copyState(); + } else { + throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing()); + } + } + } + } finally { + executor.shutdown(); + } + } + + private Dictionary<String, String> getMavenConfig() throws IOException { + Hashtable<String, String> props = new Hashtable<>(); + if (configurationAdmin != null) { + Configuration config = configurationAdmin.getConfiguration("org.ops4j.pax.url.mvn", null); + if (config != null) { + Dictionary<String, Object> cfg = config.getProperties(); + if (cfg != null) { + for (Enumeration<String> e = cfg.keys(); e.hasMoreElements(); ) { + String key = e.nextElement(); + Object val = cfg.get(key); + if (key != null) { + props.put(key, val.toString()); + } + } } } } + return props; } public void print(String message, boolean verbose) { http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java ---------------------------------------------------------------------- diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java index 999c212..7c0deed 100644 --- a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java +++ b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java @@ -18,19 +18,26 @@ package org.apache.karaf.features.internal.support; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import org.apache.karaf.features.internal.download.DownloadCallback; +import org.apache.karaf.features.internal.download.DownloadManager; +import org.apache.karaf.features.internal.download.Downloader; import org.apache.karaf.features.internal.download.StreamProvider; -import org.apache.karaf.features.internal.download.simple.SimpleDownloader; +import org.apache.karaf.features.internal.util.MultiException; -public class TestDownloadManager extends SimpleDownloader { +public class TestDownloadManager implements DownloadManager, Downloader { + private final MultiException exception = new MultiException("Error"); + private final ConcurrentMap<String, StreamProvider> providers = new ConcurrentHashMap<>(); private final Class loader; private final String dir; @@ -40,17 +47,44 @@ public class TestDownloadManager extends SimpleDownloader { } @Override + public Downloader createDownloader() { + return this; + } + + @Override + public Map<String, StreamProvider> getProviders() { + return providers; + } + + @Override + public void await() throws InterruptedException, MultiException { + exception.throwIfExceptions(); + } + + @Override + public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException { + if (!providers.containsKey(location)) { + providers.putIfAbsent(location, createProvider(location)); + } + try { + if (downloadCallback != null) { + downloadCallback.downloaded(providers.get(location)); + } + } catch (Exception e) { + exception.addSuppressed(e); + } + } + protected StreamProvider createProvider(String location) throws MalformedURLException { return new TestProvider(location); } class TestProvider implements StreamProvider { + private final String location; private final IOException exception; - private final Map<String, String> headers; private final byte[] data; TestProvider(String location) { - Map<String, String> headers = null; byte[] data = null; IOException exception = null; try { @@ -59,18 +93,18 @@ public class TestDownloadManager extends SimpleDownloader { JarOutputStream jos = new JarOutputStream(baos, man); jos.close(); data = baos.toByteArray(); - headers = new HashMap<>(); - for (Map.Entry attr : man.getMainAttributes().entrySet()) { - headers.put(attr.getKey().toString(), attr.getValue().toString()); - } } catch (IOException e) { exception = e; } - this.headers = headers; + this.location = location; this.data = data; this.exception = exception; } + public String getUrl() { + return location; + } + @Override public InputStream open() throws IOException { if (exception != null) @@ -79,10 +113,9 @@ public class TestDownloadManager extends SimpleDownloader { } @Override - public Map<String, String> getMetadata() throws IOException { - if (exception != null) - throw exception; - return headers; + public File getFile() throws IOException { + throw new UnsupportedOperationException(); } + } } http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/pom.xml ---------------------------------------------------------------------- diff --git a/instance/pom.xml b/instance/pom.xml index e3a5cab..e78ab9a 100644 --- a/instance/pom.xml +++ b/instance/pom.xml @@ -82,6 +82,12 @@ </dependency> <dependency> + <groupId>org.apache.karaf.profile</groupId> + <artifactId>org.apache.karaf.profile.core</artifactId> + <optional>true</optional> + </dependency> + + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java ---------------------------------------------------------------------- diff --git a/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java b/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java index 4a302f3..5173066 100644 --- a/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java +++ b/instance/src/main/java/org/apache/karaf/instance/command/CreateCommand.java @@ -27,6 +27,7 @@ import org.apache.felix.utils.properties.Properties; import org.apache.karaf.features.command.completers.AllFeatureCompleter; import org.apache.karaf.features.command.completers.InstalledRepoUriCompleter; import org.apache.karaf.instance.core.InstanceSettings; +import org.apache.karaf.profile.command.completers.ProfileCompleter; import org.apache.karaf.shell.api.action.Argument; import org.apache.karaf.shell.api.action.Command; import org.apache.karaf.shell.api.action.Completion; @@ -71,6 +72,11 @@ public class CreateCommand extends InstanceCommandSupport @Completion(InstalledRepoUriCompleter.class) List<String> featureURLs; + @Option(name = "-p", aliases = {"--profiles"}, + description = "Profiles to install on the instance", required = false, multiValued = true) + @Completion(ProfileCompleter.class) + List<String> profiles; + @Option(name = "-v", aliases = {"--verbose"}, description = "Display actions performed by the command (disabled by default)", required = false, multiValued = false) boolean verbose = false; @@ -116,7 +122,7 @@ public class CreateCommand extends InstanceCommandSupport } Map<String, URL> textResources = getResources(textResourceLocation); Map<String, URL> binaryResources = getResources(binaryResourceLocations); - InstanceSettings settings = new InstanceSettings(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, address, textResources, binaryResources); + InstanceSettings settings = new InstanceSettings(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, address, textResources, binaryResources, profiles); getInstanceService().createInstance(instance, settings, verbose); return null; } http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java ---------------------------------------------------------------------- diff --git a/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java b/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java index 13ba69a..c8a0063 100644 --- a/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java +++ b/instance/src/main/java/org/apache/karaf/instance/core/InstanceSettings.java @@ -35,16 +35,21 @@ public class InstanceSettings { private final String address; private final Map<String, URL> textResources; private final Map<String, URL> binaryResources; + private final List<String> profiles; public InstanceSettings(int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts, List<String> featureURLs, List<String> features) { this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, "0.0.0.0"); } public InstanceSettings(int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts, List<String> featureURLs, List<String> features, String address) { - this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, address, new HashMap<String, URL>(), new HashMap<String, URL>()); + this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, address, new HashMap<String, URL>(), new HashMap<String, URL>()); } public InstanceSettings(int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts, List<String> featureURLs, List<String> features, String address, Map<String, URL> textResources, Map<String, URL> binaryResources) { + this(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, featureURLs, features, address, textResources, binaryResources, null); + } + + public InstanceSettings(int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts, List<String> featureURLs, List<String> features, String address, Map<String, URL> textResources, Map<String, URL> binaryResources, List<String> profiles) { this.sshPort = sshPort; this.rmiRegistryPort = rmiRegistryPort; this.rmiServerPort = rmiServerPort; @@ -55,6 +60,7 @@ public class InstanceSettings { this.address = address; this.textResources = textResources != null ? textResources : new HashMap<String, URL>(); this.binaryResources = binaryResources != null ? binaryResources : new HashMap<String, URL>(); + this.profiles = profiles != null ? profiles : new ArrayList<String>(); } @@ -98,6 +104,10 @@ public class InstanceSettings { return this.address; } + public List<String> getProfiles() { + return profiles; + } + @Override public boolean equals(Object o) { if (o == this) { @@ -114,7 +124,8 @@ public class InstanceSettings { (javaOpts == null ? is.javaOpts == null : javaOpts.equals(is.javaOpts)) && (featureURLs == null ? is.featureURLs == null : featureURLs.equals(is.featureURLs)) && (features == null ? is.features == null : features.equals(is.features)) && - (address == null ? is.address == null : address.equals(is.address)); + (address == null ? is.address == null : address.equals(is.address)) && + (profiles == null ? is.profiles == null : profiles.equals(is.profiles)); } @Override @@ -125,6 +136,7 @@ public class InstanceSettings { result = 31 * result + (featureURLs != null ? featureURLs.hashCode() : 0); result = 31 * result + (features != null ? features.hashCode() : 0); result = 31 * result + (address != null ? address.hashCode() : 0); + result = 31 * result + (profiles != null ? profiles.hashCode() : 0); return result; } http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java ---------------------------------------------------------------------- diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java index 83169ef..ecf563f 100644 --- a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java +++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java @@ -16,19 +16,6 @@ */ package org.apache.karaf.instance.core.internal; -import org.apache.karaf.instance.core.Instance; -import org.apache.karaf.instance.core.InstanceService; -import org.apache.karaf.instance.core.InstanceSettings; -import org.apache.karaf.instance.main.Execute; -import org.apache.karaf.jpm.Process; -import org.apache.karaf.jpm.impl.ProcessBuilderFactoryImpl; -import org.apache.karaf.jpm.impl.ScriptUtils; -import org.apache.karaf.util.StreamUtils; -import org.apache.karaf.util.locks.FileLockUtils; -import org.fusesource.jansi.Ansi; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.BufferedReader; import java.io.Closeable; import java.io.File; @@ -43,6 +30,9 @@ import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -53,6 +43,25 @@ import java.util.Properties; import java.util.Scanner; import java.util.TreeMap; +import org.apache.karaf.instance.core.Instance; +import org.apache.karaf.instance.core.InstanceService; +import org.apache.karaf.instance.core.InstanceSettings; +import org.apache.karaf.instance.main.Execute; +import org.apache.karaf.jpm.Process; +import org.apache.karaf.jpm.impl.ProcessBuilderFactoryImpl; +import org.apache.karaf.jpm.impl.ScriptUtils; +import org.apache.karaf.profile.Profile; +import org.apache.karaf.profile.ProfileBuilder; +import org.apache.karaf.profile.ProfileService; +import org.apache.karaf.util.StreamUtils; +import org.apache.karaf.util.locks.FileLockUtils; +import org.fusesource.jansi.Ansi; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class InstanceServiceImpl implements InstanceService { public static final String STORAGE_FILE = "instance.properties"; @@ -239,7 +248,7 @@ public class InstanceServiceImpl implements InstanceService { } } - private void logInfo(String message, boolean printOutput, Object... args) { + private static void logInfo(String message, boolean printOutput, Object... args) { if (LOGGER.isInfoEnabled() || printOutput) { String formatted = String.format(message, args); LOGGER.info(formatted); @@ -255,6 +264,14 @@ public class InstanceServiceImpl implements InstanceService { if (state.instances.get(name) != null) { throw new IllegalArgumentException("Instance '" + name + "' already exists"); } + if (!settings.getProfiles().isEmpty()) { + try { + ProfileApplier.verify(); + } catch (NoClassDefFoundError error) { + throw new IllegalArgumentException("Profile service package is not available"); + } + } + String loc = settings.getLocation() != null ? settings.getLocation() : name; File karafBase = new File(loc); if (!karafBase.isAbsolute()) { @@ -349,6 +366,10 @@ public class InstanceServiceImpl implements InstanceService { copyBinaryResourceToDir(resource, karafBase, binaryResources, printOutput); } + if (!settings.getProfiles().isEmpty()) { + ProfileApplier.applyProfiles(karafBase, settings.getProfiles(), printOutput); + } + String javaOpts = settings.getJavaOpts(); if (javaOpts == null || javaOpts.length() == 0) { javaOpts = DEFAULT_JAVA_OPTS; @@ -374,7 +395,7 @@ public class InstanceServiceImpl implements InstanceService { }, true); } - private void appendToPropList(org.apache.felix.utils.properties.Properties p, String key, List<String> elements) { + private static void appendToPropList(org.apache.felix.utils.properties.Properties p, String key, List<String> elements) { if (elements == null) { return; } @@ -761,7 +782,7 @@ public class InstanceServiceImpl implements InstanceService { Socket s = new Socket(host, port); s.getOutputStream().write(shutdown.getBytes()); s.close(); - long stopTimeout = Long.parseLong(props.getProperty(KARAF_SHUTDOWN_TIMEOUT, + long stopTimeout = Long.parseLong(props.getProperty(KARAF_SHUTDOWN_TIMEOUT, Long.toString(getStopTimeout()))); long t = System.currentTimeMillis() + stopTimeout; do { @@ -1000,7 +1021,7 @@ public class InstanceServiceImpl implements InstanceService { } } - private void println(String st) { + private static void println(String st) { System.out.println(st); } @@ -1275,4 +1296,44 @@ public class InstanceServiceImpl implements InstanceService { }, true); } + private static class ProfileApplier { + + // Verify that profile package is wired correctly + static void verify() { + Profile.class.getName(); + } + + static void applyProfiles(File karafBase, List<String> profiles, boolean printOutput) throws IOException { + BundleContext bundleContext = FrameworkUtil.getBundle(ProfileApplier.class).getBundleContext(); + ServiceReference<ProfileService> reference = bundleContext.getServiceReference(ProfileService.class); + ProfileService service = bundleContext.getService(reference); + + Profile profile = ProfileBuilder.Factory.create("temp") + .addParents(profiles) + .getProfile(); + Profile overlay = service.getOverlayProfile(profile); + final Profile effective = service.getEffectiveProfile(overlay, false); + + Map<String, byte[]> configs = effective.getFileConfigurations(); + for (Map.Entry<String, byte[]> config : configs.entrySet()) { + String pid = config.getKey(); + if (!pid.equals(Profile.INTERNAL_PID + Profile.PROPERTIES_SUFFIX)) { + Path configFile = Paths.get(karafBase.toString(), "etc", pid); + logInfo("Creating file: %s", printOutput, configFile.toString()); + Files.write(configFile, config.getValue()); + } + } + FileLockUtils.execute(new File(karafBase, FEATURES_CFG), new FileLockUtils.RunnableWithProperties() { + public void run(org.apache.felix.utils.properties.Properties properties) throws IOException { + appendToPropList(properties, "featuresBoot", effective.getFeatures()); + appendToPropList(properties, "featuresRepositories", effective.getRepositories()); + } + }, true); + + bundleContext.ungetService(reference); + } + + + } + } http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 2bc1b80..84342d3 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ <module>jdbc</module> <module>jms</module> <module>jpa</module> + <module>profile</module> <module>tooling</module> <module>assemblies</module> <module>demos</module> @@ -164,7 +165,7 @@ <felix.framework.security.version>2.4.0</felix.framework.security.version> <felix.gogo.version>0.14.0</felix.gogo.version> <felix.plugin.version>2.5.3</felix.plugin.version> - <felix.utils.version>1.6.0</felix.utils.version> + <felix.utils.version>1.6.1-SNAPSHOT</felix.utils.version> <felix.webconsole.version>4.2.2</felix.webconsole.version> <felix.webconsole.api.version>3.1.2</felix.webconsole.api.version> <felix.metatype.version>1.0.10</felix.metatype.version> @@ -232,7 +233,7 @@ <pax.exam.version>4.3.0</pax.exam.version> <pax.logging.version>1.8.1</pax.logging.version> <pax.base.version>1.5.0</pax.base.version> - <pax.url.version>2.2.0</pax.url.version> + <pax.url.version>2.2.1-SNAPSHOT</pax.url.version> <pax.web.version>4.0.0</pax.web.version> <pax.tinybundle.version>2.1.0</pax.tinybundle.version> <portlet-api.version>2.0</portlet-api.version> @@ -541,6 +542,12 @@ </dependency> <dependency> + <groupId>org.apache.karaf.profile</groupId> + <artifactId>org.apache.karaf.profile.core</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> <groupId>org.apache.karaf.diagnostic</groupId> <artifactId>org.apache.karaf.diagnostic.boot</artifactId> <version>${project.version}</version> @@ -1269,6 +1276,11 @@ <artifactId>slf4j-simple</artifactId> <version>${slf4j.version}</version> </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jcl-over-slf4j</artifactId> + <version>${slf4j.version}</version> + </dependency> <dependency> <groupId>org.ops4j.pax.logging</groupId>
