blob: 7229c7bdab660013dc21f3cefb1175343aaa1e69 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2013, 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.rj.server.util;
import static org.eclipse.statet.rj.server.util.ServerUtils.RJ_SERVER_ID;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
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.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.runtime.CommonsRuntime;
import org.eclipse.statet.jcommons.runtime.bundle.BundleEntry;
import org.eclipse.statet.jcommons.runtime.bundle.BundleEntryProvider;
import org.eclipse.statet.jcommons.runtime.bundle.BundleResolver;
import org.eclipse.statet.jcommons.runtime.bundle.BundleSpec;
import org.eclipse.statet.jcommons.runtime.bundle.DefaultBundleResolver;
import org.eclipse.statet.jcommons.runtime.bundle.RefClassBundleResolver;
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.rj.RjInitFailedException;
import org.eclipse.statet.rj.RjInvalidConfigurationException;
/**
* Helps to setup a server.
*
* <h4>System properties:</h4>
* <table>
* <tr>
* <th>Name</th>
* <th>Description</th>
* <th>Default</th>
* </tr>
* <tr>
* <td><code>org.eclipse.statet.rj.context.BundleResolvers<code></td>
* <td>Comma separated list of resolvers which will be used to resolve library bundles in the
* specified order.<br/>
* Possible value: default, RefClass</td>
* <td>default</td>
* </tr>
* <table>
*/
@NonNullByDefault
public class RJContext {
public static final String RJ_BUNDLE_RESOLVERS_PROPERTY_KEY= "org.eclipse.statet.rj.context.BundleResolvers"; //$NON-NLS-1$
public static final String RJ_SERVER_CLASS_PATH_PROPERTY_KEY= "org.eclipse.statet.rj.server.ClassPath.urls"; //$NON-NLS-1$
public static final String RJ_PATH_SEPARATOR= ":,:"; //$NON-NLS-1$
private static final Pattern RJ_PATH_SEPARATOR_PATTERN= Pattern.compile(RJ_PATH_SEPARATOR, Pattern.LITERAL);
protected static final String LOCALHOST_POLICY_FILENAME= "localhost.policy"; //$NON-NLS-1$
public static BundleEntryProvider detectRJLibPaths() throws RjInitFailedException {
List<Path> expliciteBaseDirectories= null;
{ final String paths= System.getProperty("org.eclipse.statet.rj.Path"); //$NON-NLS-1$
if (paths != null) {
expliciteBaseDirectories= new ArrayList<>();
final String[] directories= RJ_PATH_SEPARATOR_PATTERN.split(paths);
for (final String pathString : directories) {
expliciteBaseDirectories.add(Paths.get(pathString));
}
}
}
try {
return BundleEntryProvider.detectEntryProvider(RJContext.class, expliciteBaseDirectories);
}
catch (final StatusException e) {
throw new RjInitFailedException("Failed to autodetect RJ library location", e);
}
}
private ImList<BundleEntryProvider> libPathEntryProviders;
private final ImList<BundleResolver> bundleResolvers;
public RJContext(final Collection<BundleEntryProvider> pathEntryProviders) {
this.libPathEntryProviders= ImCollections.toList(pathEntryProviders);
this.bundleResolvers= ImCollections.toList(loadBundleResolvers());
}
public RJContext(final BundleEntryProvider pathEntryProvider,
final BundleEntryProvider ... pathEntryProviderAdditionals) {
this(ImCollections.addElement(
ImCollections.newList(pathEntryProviderAdditionals),
0, pathEntryProvider) );
}
protected RJContext() {
this.bundleResolvers= ImCollections.toList(loadBundleResolvers());
}
protected ImList<BundleResolver> getBundleResolvers() {
return this.bundleResolvers;
}
protected List<BundleResolver> loadBundleResolvers() {
final List<BundleResolver> resolvers= new ArrayList<>();
String[] ids;
final String property= System.getProperty(RJ_BUNDLE_RESOLVERS_PROPERTY_KEY);
if (property != null) {
ids= property.split(","); //$NON-NLS-1$
}
else {
ids= new String[] { "default" }; //$NON-NLS-1$
}
for (final String id : ids) {
try {
final BundleResolver resolver= getBundleResolver(id);
if (resolver == null) {
throw new RjInvalidConfigurationException("The specified bundle resolver is unknown.");
}
resolvers.add(resolver);
}
catch (final RjInvalidConfigurationException e) {
CommonsRuntime.log(new ErrorStatus(RJ_SERVER_ID,
String.format("An error occurred when loading bundle resolver '%1$s' for RJ context.", id),
e ));
}
}
return resolvers;
}
protected @Nullable BundleResolver getBundleResolver(final String id)
throws RjInvalidConfigurationException {
if (id.equals("default")) { //$NON-NLS-1$
return new DefaultBundleResolver();
}
if (id.equals("RefClass")) { //$NON-NLS-1$
return new RefClassBundleResolver();
}
return null;
}
public List<BundleEntry> resolveBundles(final List<BundleSpec> bundleSpecs)
throws RjInvalidConfigurationException {
final List<? extends BundleEntry> candidates= getBundleCandidates();
Collections.sort(candidates);
final BundleResolver.Data data= new BundleResolver.Data(ImCollections.toList(candidates));
final List<Status> bundleStatuses= new ArrayList<>();
final List<BundleSpec> missing= new ArrayList<>();
final ImList<BundleResolver> resolvers= getBundleResolvers();
for (final BundleSpec bundleSpec : bundleSpecs) {
final MultiStatus status= new MultiStatus(RJ_SERVER_ID,
String.format("'%1$s':", bundleSpec.getId()) ); //$NON-NLS-1$
boolean found= false;
for (final BundleResolver resolver : resolvers) {
if (resolver.resolveBundle(data, bundleSpec, status)) {
found= true;
break;
}
}
// not found
if (!found) {
missing.add(bundleSpec);
status.add(new ErrorStatus(RJ_SERVER_ID, "The bundle could not be resolved."));
}
if (!status.getChildren().isEmpty()) {
bundleStatuses.add(status);
}
}
if (!bundleStatuses.isEmpty()) {
final Status status= new MultiStatus(RJ_SERVER_ID,
"Status for resolving of bundles for RJ.",
null, ImCollections.toList(bundleStatuses) );
CommonsRuntime.log(status);
if (!missing.isEmpty()) {
throw new RjInvalidConfigurationException("Failed to resolve the bundle(s): " + CollectionUtils.toString(missing, ", "), //$NON-NLS-2$
new StatusException(status) );
}
}
return ImCollections.toList(data.getResolved());
}
protected List<? extends BundleEntry> getBundleCandidates() {
final List<BundleEntry> pathsEntries= new ArrayList<>();
for (final BundleEntryProvider provider : this.libPathEntryProviders) {
provider.getEntries(pathsEntries);
}
return pathsEntries;
}
public String getServerPolicyFilePath() throws RjInvalidConfigurationException {
final URL resource= RJContext.class.getClassLoader().getResource(LOCALHOST_POLICY_FILENAME);
if (resource != null) {
resource.toString();
}
try {
final BundleEntry lib= resolveBundles(ImCollections.newList(ServerUtils.RJ_SERVER_SPEC)).get(0);
final Path policyPath= lib.getResourcePath(LOCALHOST_POLICY_FILENAME);
if (policyPath != null && Files.isRegularFile(policyPath)) {
return policyPath.toUri().toString();
}
else {
final String s= lib.getResourceUrlString(LOCALHOST_POLICY_FILENAME);
if (s != null) {
return s;
}
}
throw new UnsupportedOperationException(String.format("BundleEntry= '%1$s'", lib)); //$NON-NLS-1$
}
catch (final Exception e) {
throw new RjInvalidConfigurationException("Failed find server policy file.", e);
}
}
protected String getPropertiesDirPath() {
return System.getProperty("user.dir"); //$NON-NLS-1$
}
protected @Nullable InputStream getInputStream(final String path) throws IOException {
final File file= new File(path);
if (!file.exists()) {
return null;
}
return new FileInputStream(file);
}
protected OutputStream getOutputStream(final String path) throws IOException {
final File file= new File(path);
return new FileOutputStream(file, false);
}
public @Nullable Properties loadProperties(final String name) throws IOException {
if (name == null) {
throw new NullPointerException("name"); //$NON-NLS-1$
}
final String path= getPropertiesDirPath() + '/' + name + ".properties"; //$NON-NLS-1$
final InputStream in= getInputStream(path);
if (in == null) {
return null;
}
final Properties properties= new Properties();
try {
properties.load(in);
}
finally {
if (in != null) {
try {
in.close();
}
catch (final IOException e) {}
}
}
return properties;
}
public void saveProperties(final String name, final Properties properties) throws IOException {
if (name == null) {
throw new NullPointerException("name"); //$NON-NLS-1$
}
if (properties == null) {
throw new NullPointerException("properties"); //$NON-NLS-1$
}
final String path= getPropertiesDirPath() + '/' + name + ".properties"; //$NON-NLS-1$
final OutputStream out= getOutputStream(path);
try {
properties.store(out, null);
}
finally {
if (out != null) {
try {
out.close();
}
catch (final IOException e) {}
}
}
}
}