diff --git a/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/i18n/BaseMessages.properties b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/i18n/BaseMessages.properties index dccc177ab..86b3d0f2c 100644 --- a/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/i18n/BaseMessages.properties +++ b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/i18n/BaseMessages.properties @@ -47,3 +47,4 @@ Registry.retrieve_absent_exception=Service required for id %s is absent in the r Requirements.mandatory_requirements_resolvers_demand_author=Passage Core Requirements.mandatory_requirements_resolvers_demand=Mandatory requirement resolvers structure RuntimeRegistry.unregister_absent=Service to be unregistered (%s) is not yet contained in the registry +Settings.error_on_reading_settings=Looking for settings file failed: diff --git a/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/PassageFileExtension.java b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/PassageFileExtension.java index 18209dc14..0d7b4792a 100644 --- a/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/PassageFileExtension.java +++ b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/PassageFileExtension.java @@ -12,11 +12,16 @@ *******************************************************************************/ package org.eclipse.passage.lic.internal.base.io; +import java.nio.file.Path; import java.util.function.Supplier; -public interface PassageFileExtension extends Supplier { +public abstract class PassageFileExtension implements Supplier { - public static final class LicenseEncrypted implements PassageFileExtension { + public final boolean ends(Path path) { + return path.getFileName().toString().endsWith(get()); + } + + public static final class LicenseEncrypted extends PassageFileExtension { @Override public String get() { @@ -25,7 +30,7 @@ public String get() { } - public static final class LicenseDecrypted implements PassageFileExtension { + public static final class LicenseDecrypted extends PassageFileExtension { @Override public String get() { @@ -34,7 +39,7 @@ public String get() { } - public static final class PublicKey implements PassageFileExtension { + public static final class PublicKey extends PassageFileExtension { @Override public String get() { @@ -43,7 +48,7 @@ public String get() { } - public static final class PrivateKey implements PassageFileExtension { + public static final class PrivateKey extends PassageFileExtension { @Override public String get() { @@ -52,7 +57,7 @@ public String get() { } - public static final class Settings implements PassageFileExtension { + public static final class Settings extends PassageFileExtension { @Override public String get() { diff --git a/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/Settings.java b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/Settings.java new file mode 100644 index 000000000..15fe7e750 --- /dev/null +++ b/bundles/org.eclipse.passage.lic.base/src/org/eclipse/passage/lic/internal/base/io/Settings.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2020 ArSysOp + * + * 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/. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ArSysOp - initial API and implementation + *******************************************************************************/ +package org.eclipse.passage.lic.internal.base.io; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.eclipse.passage.lic.internal.api.LicensingException; +import org.eclipse.passage.lic.internal.base.i18n.BaseMessages; + +/** + *

+ * Traverses all the files down to the given {@code base} directory looking for + * settings files. These ones are read as property-files one by one and + * merged into the final map of properties, until {@code enough} predicate (if + * any) says these is no need to proceed. + *

+ * + * @see PassageFileExtension.Settings + */ +@SuppressWarnings("restriction") +public final class Settings { + + private final Supplier base; + private final Predicate> enough; + + /** + * Tell me where to start searching and when to stop + * + * @param base + * @param enough + */ + public Settings(Supplier base, Predicate> enough) { + this.base = base; + this.enough = enough; + } + + /** + * Load all settings files located under the given {@code base} directory + */ + public Settings(Supplier base) { + this(base, map -> false); + } + + public Map get() throws LicensingException { + Map properties = new HashMap<>(); + try { + Files.walkFileTree(base.get(), new HunterForSettingsFiles(properties)); + } catch (IOException e) { + throw new LicensingException(BaseMessages.getString("Settings.error_on_reading_settings"), e); //$NON-NLS-1$ + } + return properties; + } + + private final class HunterForSettingsFiles extends SimpleFileVisitor { + + private final Map properties; + private final PassageFileExtension extension = new PassageFileExtension.Settings(); + + public HunterForSettingsFiles(Map properties) { + this.properties = properties; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (!extension.ends(file)) { + return FileVisitResult.CONTINUE; + } + load(file).stream() // + .forEach(e -> properties.put(// + e.getKey().toString(), // + e.getValue())); + return enough.test(properties) ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + private Set> load(Path file) throws IOException { + Properties heap = new Properties(); + try (InputStream stream = new FileInputStream(file.toFile())) { + heap.load(stream); + } + return heap.entrySet(); + } + + } + +} diff --git a/bundles/org.eclipse.passage.lic.hc/META-INF/MANIFEST.MF b/bundles/org.eclipse.passage.lic.hc/META-INF/MANIFEST.MF index 52ce0a3d4..423e4321f 100644 --- a/bundles/org.eclipse.passage.lic.hc/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.passage.lic.hc/META-INF/MANIFEST.MF @@ -14,7 +14,8 @@ Require-Bundle: org.apache.httpcomponents.httpcore;bundle-version="0.0.0", org.eclipse.osgi.services;bundle-version="0.0.0", org.eclipse.passage.lic.equinox;bundle-version="0.0.0", org.eclipse.passage.lic.net;bundle-version="0.0.0", - org.eclipse.passage.lic.api + org.eclipse.passage.lic.api, + org.eclipse.passage.lic.base Export-Package: org.eclipse.passage.lic.hc, org.eclipse.passage.lic.internal.hc;x-internal:=true, org.eclipse.passage.lic.internal.hc.remote;x-internal:=true, diff --git a/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.java b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.java index 9929e04e1..41a99b1af 100644 --- a/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.java +++ b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.java @@ -24,6 +24,8 @@ public class HcMessages extends NLS { public static String HttpClient_final_error_message; public static String HttpClient_not_ok_response; + public static String LicensingServerCoordinates_settings_not_found; + static { NLS.initializeMessages(BUNDLE_NAME, HcMessages.class); } diff --git a/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.properties b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.properties index 71c4b4700..61fb1dbcc 100644 --- a/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.properties +++ b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/i18n/HcMessages.properties @@ -15,3 +15,4 @@ DecryptedConditions_no_transport_for_content_type=Unknown content type: no regis DecryptedConditions_reading_error=Error reading remote condition data HttpClient_final_error_message=Error mining conditions HttpClient_not_ok_response=Mining connection responded not OK: %d ($s) +LicensingServerCoordinates_settings_not_found=%s is not found diff --git a/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/remote/LicensingServerCoordinates.java b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/remote/LicensingServerCoordinates.java new file mode 100644 index 000000000..c31dad06a --- /dev/null +++ b/bundles/org.eclipse.passage.lic.hc/src/org/eclipse/passage/lic/internal/hc/remote/LicensingServerCoordinates.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2020 ArSysOp + * + * 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/. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ArSysOp - initial API and implementation + *******************************************************************************/ +package org.eclipse.passage.lic.internal.hc.remote; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import org.eclipse.passage.lic.internal.api.LicensingException; +import org.eclipse.passage.lic.internal.base.StringNamedData; +import org.eclipse.passage.lic.internal.base.io.Settings; +import org.eclipse.passage.lic.internal.hc.i18n.HcMessages; +import org.eclipse.passage.lic.internal.net.LicensingServerHost; +import org.eclipse.passage.lic.internal.net.LicensingServerPort; + +/** + *

+ * On demand reads the available settings, retrieves {@code host} and + * {@code port} coordinates of licensing server and forms corresponding part of + * URL. + *

+ */ +@SuppressWarnings("restriction") +public final class LicensingServerCoordinates { + + private final Supplier settings; + + /** + * Instructed to look for settings files starting from the given directory + * + * @param residence + */ + public LicensingServerCoordinates(Supplier residence) { + this.settings = residence; + } + + /** + * + * @return licensing server location in a form {@code host:port} + * @throws LicensingException in case of errors during file reading or setting + * analysis + */ + public String get() throws LicensingException { + Map properties = new Settings(settings, this::necessaryPropertiesExist).get(); + return String.format(// + "%s:%s", //$NON-NLS-1$ + value(new LicensingServerHost(properties)), // + value(new LicensingServerPort(properties))); + } + + private String value(StringNamedData data) throws LicensingException { + Optional value = data.get(); + if (value.isPresent()) { + throw new LicensingException( + String.format(HcMessages.LicensingServerCoordinates_settings_not_found, data.key())); + } + return value.get(); + } + + private boolean necessaryPropertiesExist(Map properties) { + return new LicensingServerHost(properties).get().isPresent() && // + new LicensingServerPort(properties).get().isPresent(); + } + +} diff --git a/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerHost.java b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerHost.java new file mode 100644 index 000000000..5cdee6e22 --- /dev/null +++ b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerHost.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2020 ArSysOp + * + * 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/. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ArSysOp - initial API and implementation + *******************************************************************************/ +package org.eclipse.passage.lic.internal.net; + +import java.util.Map; + +import org.eclipse.passage.lic.internal.base.StringNamedData; + +@SuppressWarnings("restriction") +public final class LicensingServerHost extends StringNamedData { + + public LicensingServerHost(Map container) { + super(container); + } + + @Override + public String key() { + return "licensing.server.host"; //$NON-NLS-1$ + } + +} diff --git a/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerPort.java b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerPort.java new file mode 100644 index 000000000..45e3fb605 --- /dev/null +++ b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/internal/net/LicensingServerPort.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2020 ArSysOp + * + * 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/. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ArSysOp - initial API and implementation + *******************************************************************************/ +package org.eclipse.passage.lic.internal.net; + +import java.util.Map; + +import org.eclipse.passage.lic.internal.base.StringNamedData; + +@SuppressWarnings("restriction") +public final class LicensingServerPort extends StringNamedData { + + public LicensingServerPort(Map container) { + super(container); + } + + @Override + public String key() { + return "licensing.server.port"; //$NON-NLS-1$ + } + +} diff --git a/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/net/LicensingNet.java b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/net/LicensingNet.java index 3abc6a471..e393ef990 100644 --- a/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/net/LicensingNet.java +++ b/bundles/org.eclipse.passage.lic.net/src/org/eclipse/passage/lic/net/LicensingNet.java @@ -12,6 +12,9 @@ *******************************************************************************/ package org.eclipse.passage.lic.net; +import org.eclipse.passage.lic.internal.net.LicensingServerHost; +import org.eclipse.passage.lic.internal.net.LicensingServerPort; + /** * * @since 0.5.0 @@ -19,7 +22,15 @@ */ public class LicensingNet { + /** + * @deprecated use {@link LicensingServerHost} + */ + @Deprecated public static final String LICENSING_SERVER_HOST = "licensing.server.host"; //$NON-NLS-1$ + /** + * @deprecated use {@link LicensingServerPort} + */ + @Deprecated public static final String LICENSING_SERVER_PORT = "licensing.server.port"; //$NON-NLS-1$ public static final String ROLE = "role"; //$NON-NLS-1$ diff --git a/tests/org.eclipse.passage.lic.base.tests/src/org/eclipse/passage/lic/internal/base/tests/io/SettingsTest.java b/tests/org.eclipse.passage.lic.base.tests/src/org/eclipse/passage/lic/internal/base/tests/io/SettingsTest.java new file mode 100644 index 000000000..b37674e03 --- /dev/null +++ b/tests/org.eclipse.passage.lic.base.tests/src/org/eclipse/passage/lic/internal/base/tests/io/SettingsTest.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2020 ArSysOp + * + * 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/. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * ArSysOp - initial API and implementation + *******************************************************************************/ +package org.eclipse.passage.lic.internal.base.tests.io; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Map; + +import org.eclipse.passage.lic.internal.api.LicensingException; +import org.eclipse.passage.lic.internal.base.io.PassageFileExtension; +import org.eclipse.passage.lic.internal.base.io.Settings; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +@SuppressWarnings("restriction") +public final class SettingsTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void enoughStopsSearching() throws IOException, LicensingException { + // given + String key = "common_key"; //$NON-NLS-1$ + writeOverlappingSettings(key + " = some_value", new PassageFileExtension.Settings()); //$NON-NLS-1$ + // when + Map properties = new Settings(// + folder.getRoot()::toPath, // + m -> m.containsKey(key)).get(); + // then + assertTrue(properties.containsKey(key)); + assertEquals(3, properties.size()); // only one file is loaded + } + + @Test + public void neverEnoughDoesNotStopSearching() throws IOException, LicensingException { + // given + writeOverlappingSettings("x=X", new PassageFileExtension.Settings()); //$NON-NLS-1$ + // when + Map properties = new Settings(folder.getRoot()::toPath).get(); + // then + assertEquals(7, properties.size()); // all files are loaded + } + + @Test + public void onlySettingsFilesAreAnalyzed() throws IOException, LicensingException { + // given + writeOverlappingSettings("s=S", new PassageFileExtension.LicenseDecrypted()); //$NON-NLS-1$ + // when + Map properties = new Settings(folder.getRoot()::toPath).get(); + // then + assertEquals(0, properties.size()); // all files are loaded + } + + private void writeOverlappingSettings(String common, PassageFileExtension extension) throws IOException { + writeOneMoreFile(extension, common, "a=A", "b=B"); //$NON-NLS-1$//$NON-NLS-2$ + writeOneMoreFile(extension, common, "c=C", "d=D"); //$NON-NLS-1$//$NON-NLS-2$ + writeOneMoreFile(extension, common, "e=E", "f=F"); //$NON-NLS-1$//$NON-NLS-2$ + } + + private void writeOneMoreFile(PassageFileExtension extension, String... extras) throws IOException { + File file = folder.newFile(Long.toHexString(System.nanoTime()) + extension.get()); + try (PrintWriter writer = new PrintWriter(file)) { + Arrays.stream(extras).forEach(writer::println); + } + } + +}