blob: 52e01cdc6bee0a5e0620e6e8e3e305c54e661adf [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2019, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.jcommons.runtime;
import static org.eclipse.statet.internal.jcommons.runtime.CommonsRuntimeInternals.BUNDLE_ID;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import org.eclipse.statet.jcommons.collections.CollectionUtils;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.Disposable;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.lang.ObjectUtils.ToStringBuilder;
import org.eclipse.statet.jcommons.runtime.bundle.BundleEntry;
import org.eclipse.statet.jcommons.runtime.bundle.BundleResolver;
import org.eclipse.statet.jcommons.runtime.bundle.BundleSpec;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.MultiStatus;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.StatusLogger;
/**
* Basic implementation of {@link AppEnvironment} for non-Eclipse/Osgi environments.
*
* <p>This class is intend to be extended:</p>
* <ul>
* <li>Add handling for stop of the application:
* Implementations have to call {@link #onAppStopping()}.
* <br/>{@link #initJRuntimeStoppingHandler()} can be use for simple Java runtime mechanism.</li>
* <li>Provide logging support:
* Provide a {@link StatusLogger status logger} or overwrite {@link #logs}.</li>
* </ul>
*/
@NonNullByDefault
public abstract class BasicAppEnvironment implements AppEnvironment {
private final String envId;
private final CopyOnWriteArraySet<Disposable> stoppingListeners= new CopyOnWriteArraySet<>();
private final StatusLogger statusLogger;
private final @Nullable ImList<? extends BundleResolver> bundleResolvers;
/**
* Creates a new app environment.
*
* @param envId the environment id
* @param statusLogger the status logger to use
* @param bundleResolvers the bundle resolvers
*/
public BasicAppEnvironment(final String envId, final StatusLogger statusLogger,
final List<? extends BundleResolver> bundleResolvers) {
this.envId= nonNullAssert(envId);
this.statusLogger= statusLogger;
this.bundleResolvers= ImCollections.toNonNullList(bundleResolvers);
}
/**
* Creates a new app environment.
*
* @param envId the environment id
* @param statusLogger the status logger to use
* @param bundleResolvers the bundle resolver
*/
public BasicAppEnvironment(final String envId, final StatusLogger statusLogger,
final BundleResolver bundleResolver) {
this.envId= nonNullAssert(envId);
this.statusLogger= statusLogger;
this.bundleResolvers= ImCollections.newList(nonNullAssert(bundleResolver));
}
/**
* Creates a new app environment without bundle resolution.
*
* @param envId the environment id
* @param statusLogger the status logger to use
*/
public BasicAppEnvironment(final String envId, final StatusLogger statusLogger) {
this.envId= nonNullAssert(envId);
this.statusLogger= statusLogger;
this.bundleResolvers= null;
}
@Override
public final String getEnvId() {
return this.envId;
}
protected void initJRuntimeStoppingHandler() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
onAppStopping();
}
});
}
@Override
public List<BundleEntry> resolveBundles(final List<BundleSpec> bundleSpecs)
throws StatusException {
final ImList<? extends BundleResolver> bundleResolvers= this.bundleResolvers;
if (bundleResolvers == null) {
throw new UnsupportedOperationException();
}
final LinkedHashSet<BundleEntry> resolved= new LinkedHashSet<>();
final List<Status> statusCollector= new ArrayList<>();
final List<BundleSpec> missing= new ArrayList<>();
for (final BundleSpec bundleSpec : bundleSpecs) {
if (!resolveBundle(bundleResolvers, bundleSpec, resolved, statusCollector)) {
missing.add(bundleSpec);
}
}
if (!statusCollector.isEmpty()) {
final ImList<Status> statusChildren= ImCollections.toList(statusCollector);
CommonsRuntime.log(new MultiStatus(BUNDLE_ID,
"Status for resolving of bundles.", //$NON-NLS-1$
null, statusChildren ));
if (!missing.isEmpty()) {
throw new StatusException(new MultiStatus(BUNDLE_ID,
"Failed to resolve the bundle(s): " + CollectionUtils.toString(missing, ", "), //$NON-NLS-2$
null, statusChildren ));
}
}
return ImCollections.toList(resolved);
}
public boolean resolveBundle(final BundleSpec bundleSpec,
final LinkedHashSet<BundleEntry> resolved, final List<Status> statusCollector) {
final ImList<? extends BundleResolver> bundleResolvers= this.bundleResolvers;
if (bundleResolvers == null) {
throw new UnsupportedOperationException();
}
return resolveBundle(bundleResolvers, bundleSpec, resolved, statusCollector);
}
private boolean resolveBundle(final ImList<? extends BundleResolver> bundleResolvers,
final BundleSpec bundleSpec,
final LinkedHashSet<BundleEntry> resolved, final List<Status> statusCollector) {
final MultiStatus status= new MultiStatus(BUNDLE_ID,
String.format("'%1$s':", bundleSpec.getId()) ); //$NON-NLS-1$
boolean found= false;
for (final BundleResolver resolver : bundleResolvers) {
if (resolver.resolveBundle(bundleSpec, resolved, status)) {
found= true;
break;
}
}
if (!found) {
status.add(new ErrorStatus(BUNDLE_ID, "The bundle could not be resolved."));
}
if (!status.getChildren().isEmpty()) {
statusCollector.add(status);
}
return found;
}
@Override
public void log(final Status status) {
this.statusLogger.log(status);
}
@Override
public void addStoppingListener(final Disposable listener) {
this.stoppingListeners.add(listener);
}
@Override
public void removeStoppingListener(final Disposable listener) {
this.stoppingListeners.remove(listener);
}
protected void onAppStopping() {
for (final Disposable listener : this.stoppingListeners) {
try {
listener.dispose();
}
catch (final Throwable e) {
log(new ErrorStatus(BUNDLE_ID,
"An error occured when disposing app listener.",
e ));
}
}
this.stoppingListeners.clear();
}
@Override
public String toString() {
final ToStringBuilder sb= new ToStringBuilder("AppEnvironment", getClass()); //$NON-NLS-1$
sb.append(' ', this.envId);
sb.addProp("bundleResolvers", this.bundleResolvers); //$NON-NLS-1$
return sb.toString();
}
}