Further improved implementation for
https://bugs.eclipse.org/bugs/show_bug.cgi?id=569910
Added EDEFProperties class and methods for both loading and storing EDEF
properties
Change-Id: Icc16f3f1e489d4000d59da61b85a852d865b6bc3
diff --git a/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/default.properties b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/default.properties
new file mode 100644
index 0000000..a11b757
--- /dev/null
+++ b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/default.properties
@@ -0,0 +1,4 @@
+service.imported=true
+endpoint.framework.uuid:uuid=
+endpoint.id:uuid=
+endpoint.service.id:Long=0
diff --git a/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/default.properties b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/default.properties
index 9c2e569..5ac3080 100644
--- a/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/default.properties
+++ b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/default.properties
@@ -1,5 +1,4 @@
# other properties required by RSA specification (chap 122 in compendium spec)
-service.imported=true
-endpoint.framework.uuid:uuid=
-endpoint.id:uuid=
-endpoint.service.id:Long=0
+service.imported.configs:array=ecf.generic.server
+remote.configs.supported:array=ecf.generic.server
+remote.intents.supported:array=passByValue,exactlyOnce,ordered
diff --git a/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/timeserviceendpointdescription.xml b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/timeserviceendpointdescription.xml
index d2fda97..0e5e75b 100644
--- a/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/timeserviceendpointdescription.xml
+++ b/examples/bundles/com.mycorp.examples.timeservice.consumer.filediscovery/edef/timeserviceendpointdescription.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<endpoint-descriptions xmlns="http://www.osgi.org/xmlns/rsa/v1.0.0">
<endpoint-description>
+ <property value-type="String" name="foo" value="bar"/>
</endpoint-description>
</endpoint-descriptions>
\ No newline at end of file
diff --git a/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EDEFProperties.java b/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EDEFProperties.java
new file mode 100644
index 0000000..e8299dc
--- /dev/null
+++ b/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EDEFProperties.java
@@ -0,0 +1,543 @@
+/****************************************************************************
+ * Copyright (c) 2020 Composent, Inc. 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/
+ *
+ * Contributors:
+ * Composent, Inc. - initial API and implementation
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *****************************************************************************/
+package org.eclipse.ecf.osgi.services.remoteserviceadmin;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import org.eclipse.ecf.internal.osgi.services.remoteserviceadmin.DebugOptions;
+import org.eclipse.ecf.internal.osgi.services.remoteserviceadmin.LogUtility;
+
+/**
+ * Class to represent EDEF properties for load from .properties file (via {@link #loadEDEFProperties(InputStream)} or
+ * {@link #loadEDEFProperties(Reader)}) or via store to .properties file (via {@link #storeEDEFProperties(BufferedWriter, String)}
+ * or {@link #storeEDEFProperties(Writer, String)}. This class is used by the EndpointDescriptionLocator
+ * class to load from default.properties files as well as properties edeffile.properties to override
+ * the values from the edeffile.xml files specified by the Remote-Service header in manifest as per the
+ * RSA specification (chap 122 in compendium spec).
+ * @since 4.8
+ *
+ */
+public class EDEFProperties extends Properties {
+
+ private static final long serialVersionUID = -7351470248095230347L;
+ private static int nextInt = 0;
+ private static long nextLong = 0;
+ private static short nextShort = 0;
+ private static byte nextByte = 0;
+
+ public class EDEFPropertiesValue {
+ private String type1 = "string"; //$NON-NLS-1$
+ private String type2 = "string"; //$NON-NLS-1$
+ private String valueString;
+ private Object valueObject;
+
+ boolean isArray() {
+ return this.type1.equalsIgnoreCase("array"); //$NON-NLS-1$
+ }
+
+ boolean isSet() {
+ return this.type1.equalsIgnoreCase("set"); //$NON-NLS-1$
+ }
+
+ boolean isList() {
+ return this.type1.equalsIgnoreCase("list"); //$NON-NLS-1$
+ }
+
+ boolean isCollection() {
+ return isSet() || isList();
+ }
+
+ boolean isSimpleType() {
+ return !isCollection() && !isArray();
+ }
+
+ public boolean hasTypeAgreement(EDEFPropertiesValue otherValue) {
+ String otherType = (otherValue.isArray() || otherValue.isCollection()) ? otherValue.type2
+ : otherValue.type1;
+ return (isArray() || isCollection()) ? this.type2.equalsIgnoreCase(otherType)
+ : this.type1.equalsIgnoreCase(otherType);
+ }
+
+ EDEFPropertiesValue addPropertyValue(EDEFPropertiesValue newValue) {
+ if (!hasTypeAgreement(newValue)) {
+ LogUtility.logError("addEDEFPropertyValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
+ "type disagreement between property values for old type1=" + this.type1 + ",type2=" //$NON-NLS-1$ //$NON-NLS-2$
+ + this.type2 + ", and new type1=" + newValue.type1 + ",type2=" + newValue.type2); //$NON-NLS-1$ //$NON-NLS-2$
+ return this;
+ }
+ // get old and new values
+ Object ov = this.getValueObject();
+ Object nv = newValue.getValueObject();
+ // If we've got a collection already
+ if (isCollection()) {
+ Collection oldVs = (Collection) ov;
+ // If newValue is also collection
+ if (newValue.isCollection()) {
+ // Then addAll
+ oldVs.addAll((Collection) nv);
+ } else if (newValue.isSimpleType()) {
+ // Add single element
+ oldVs.add(nv);
+ }
+ } else if (isArray()) {
+ // Get old length
+ int oldLength = Array.getLength(ov);
+ Object newResultValue = null;
+ if (newValue.isArray()) {
+ // new value is also array...get length
+ int newLength = Array.getLength(nv);
+ // Check to make sure that the type of elements is same (type2 for arrays)
+ newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + newLength);
+ // copy ov contents to newResultValue
+ System.arraycopy(ov, 0, newResultValue, 0, oldLength);
+ // append nv contents to newResultValue after ov contents
+ System.arraycopy(nv, 0, newResultValue, oldLength, newLength);
+ } else if (newValue.isSimpleType()) {
+ newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + 1);
+ System.arraycopy(ov, 0, newResultValue, 0, oldLength);
+ Array.set(newResultValue, oldLength, nv);
+ }
+ if (newResultValue != null) {
+ this.valueObject = newResultValue;
+ }
+ }
+ return this;
+ }
+
+ EDEFPropertiesValue(String value) {
+ // Split value with =
+ String[] valueArr = value.split("="); //$NON-NLS-1$
+ // Split first one
+ if (valueArr.length > 1) {
+ // split second element in valueArr by :
+ String[] firstSplit = valueArr[0].split(":"); //$NON-NLS-1$
+ // If more than one then type2 is second element in firstSplit
+ if (firstSplit.length > 1) {
+ this.type2 = firstSplit[1];
+ }
+ // In either case type1 is firstSplit[0]
+ this.type1 = firstSplit[0];
+ }
+ // Now set value to the last elemtn in the valueArr
+ this.valueString = valueArr[valueArr.length - 1];
+ }
+
+ private void setType2(Collection coll) {
+ Class<?> c = coll.iterator().next().getClass();
+ if (!c.equals(String.class)) {
+ this.type2 = c.getSimpleName().toLowerCase();
+ }
+ }
+
+ EDEFPropertiesValue(Object value) {
+ Class<?> clazz = value.getClass();
+ if (clazz.isArray()) {
+ this.type1 = "array"; //$NON-NLS-1$
+ Class<?> compType = clazz.getComponentType();
+ if (!compType.equals(String.class)) {
+ this.type2 = compType.getSimpleName().toLowerCase();
+ }
+ } else if (List.class.isInstance(value)) {
+ this.type1 = "list"; //$NON-NLS-1$
+ setType2((List) value);
+ } else if (Set.class.isInstance(value)) {
+ this.type1 = "set"; //$NON-NLS-1$
+ setType2((Set) value);
+ } else {
+ String type = clazz.getSimpleName().toLowerCase();
+ if (!type.equalsIgnoreCase("string")) { //$NON-NLS-1$
+ this.type1 = type;
+ }
+ }
+ this.valueObject = value;
+
+ }
+
+ private Object getSimpleValue(Class<?> simpleType, Object value) {
+ try {
+ return simpleType.getDeclaredMethod("valueOf", new Class[] { String.class }).invoke(null, value); //$NON-NLS-1$
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+ | NoSuchMethodException | SecurityException e) {
+ LogUtility.logWarning("getSimpleValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, this.getClass(), //$NON-NLS-1$
+ "Cannot create instance of simpleType=" + simpleType + ", value=" + value); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ }
+ }
+
+ boolean isUnique() {
+ return "unique".equals(this.type2); //$NON-NLS-1$
+ }
+
+ Object readSimpleValue(String simpleType, String value) {
+ switch (simpleType) {
+ case "long": //$NON-NLS-1$
+ case "Long": //$NON-NLS-1$
+ if ("unique".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
+ return getNextLong();
+ } else if ("nanoTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
+ return System.nanoTime();
+ } else if ("milliTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
+ return System.currentTimeMillis();
+ }
+ return getSimpleValue(Long.class, value);
+ case "double": //$NON-NLS-1$
+ case "Double": //$NON-NLS-1$
+ return getSimpleValue(Double.class, value);
+ case "float": //$NON-NLS-1$
+ case "Float": //$NON-NLS-1$
+ return getSimpleValue(Float.class, value);
+ case "int": //$NON-NLS-1$
+ case "integer": //$NON-NLS-1$
+ case "Integer": //$NON-NLS-1$
+ if ("unique".equals(this.type2)) { //$NON-NLS-1$
+ return getNextInteger();
+ }
+ return getSimpleValue(Integer.class, value);
+ case "Byte": //$NON-NLS-1$
+ case "byte": //$NON-NLS-1$
+ if ("unique".equals(this.type2)) { //$NON-NLS-1$
+ return getNextByte();
+ }
+ return getSimpleValue(Byte.class, value);
+ case "char": //$NON-NLS-1$
+ case "Character": //$NON-NLS-1$
+ return getSimpleValue(Character.class, value.toCharArray()[0]);
+ case "boolean": //$NON-NLS-1$
+ case "Boolean": //$NON-NLS-1$
+ return getSimpleValue(Boolean.class, value);
+ case "short": //$NON-NLS-1$
+ case "Short": //$NON-NLS-1$
+ if ("unique".equals(this.type2)) { //$NON-NLS-1$
+ return getNextShort();
+ }
+ return getSimpleValue(Short.class, value);
+ case "uuid": //$NON-NLS-1$
+ case "Uuid": //$NON-NLS-1$
+ case "UUID": //$NON-NLS-1$
+ // we don't care whether 'unique' is given or not
+ return UUID.randomUUID().toString();
+ case "String": //$NON-NLS-1$
+ case "string": //$NON-NLS-1$
+ return value;
+ default:
+ return null;
+ }
+ }
+
+ Object readArrayValues(String collectionValue) {
+ String[] elements = this.valueString.split("\\s*,\\s*"); //$NON-NLS-1$
+ Object result = null;
+ for (int i = 0; i < elements.length; i++) {
+ Object elementValue = readSimpleValue(this.type2, elements[i]);
+ if (elementValue == null) {
+ LogUtility.logWarning("getArrayValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
+ "array element=" + elements[i] + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$
+ continue;
+ } else {
+ if (i == 0) {
+ result = Array.newInstance(elementValue.getClass(), elements.length);
+ }
+ Array.set(result, i, elementValue);
+ }
+ }
+ return result;
+ }
+
+ Collection<Object> readCollectionValues(Collection<Object> c, String collectionValue) {
+ String[] elements = this.valueString.split("\\s*,\\s*"); //$NON-NLS-1$
+ for (String element : elements) {
+ Object elementValue = readSimpleValue(this.type2, element);
+ if (elementValue == null) {
+ LogUtility.logWarning("getCollectionValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, //$NON-NLS-1$
+ getClass(), "array element=" + element + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$
+ continue;
+ } else {
+ c.add(elementValue);
+ }
+ }
+ return c;
+ }
+
+ public synchronized Object getValueObject() {
+ if (this.valueObject == null) {
+ switch (this.type1) {
+ case "array": //$NON-NLS-1$
+ this.valueObject = readArrayValues(this.valueString);
+ break;
+ case "list": //$NON-NLS-1$
+ this.valueObject = readCollectionValues(new ArrayList(), this.valueString);
+ break;
+ case "set": //$NON-NLS-1$
+ this.valueObject = readCollectionValues(new HashSet(), this.valueString);
+ break;
+ default:
+ this.valueObject = readSimpleValue(this.type1, this.valueString);
+ break;
+ }
+ }
+ return this.valueObject;
+ }
+
+ public synchronized String getEDEFPropertyValueString() {
+ if (this.valueString == null) {
+ StringBuffer buf = new StringBuffer();
+ if (isSimpleType()) {
+ if (!this.type1.equalsIgnoreCase("string")) { //$NON-NLS-1$
+ buf.append(":").append(this.type1); //$NON-NLS-1$
+ }
+ buf.append("=").append(this.valueObject.toString()); //$NON-NLS-1$
+ } else {
+ buf.append(":").append(this.type1); //$NON-NLS-1$
+ if (!this.type2.equalsIgnoreCase("string")) { //$NON-NLS-1$
+ buf.append(":").append(this.type2); //$NON-NLS-1$
+ }
+ buf.append("="); //$NON-NLS-1$
+ Object[] arr = (Object[]) (isArray() ? valueObject : ((Collection) valueObject).toArray());
+ for (int i = 0; i < arr.length; i++) {
+ buf.append(arr[i].toString());
+ if (i != (arr.length - 1)) {
+ buf.append(","); //$NON-NLS-1$
+ }
+ }
+ }
+ this.valueString = buf.toString();
+ }
+ return valueString;
+ }
+ }
+
+ synchronized static int getNextInteger() {
+ if (nextInt == Integer.MAX_VALUE) {
+ nextInt = 0;
+ }
+ return ++nextInt;
+ }
+
+ synchronized static long getNextLong() {
+ if (nextLong == Long.MAX_VALUE) {
+ nextLong = 0;
+ }
+ return ++nextLong;
+ }
+
+ synchronized static short getNextShort() {
+ if (nextShort == Short.MAX_VALUE) {
+ nextShort = 0;
+ }
+ return ++nextShort;
+ }
+
+ synchronized static byte getNextByte() {
+ if (nextByte == Byte.MAX_VALUE) {
+ nextByte = 0;
+ }
+ return ++nextByte;
+ }
+
+ /**
+ * Create empty EDEFProperties instance.
+ */
+ public EDEFProperties() {
+ }
+
+ /**
+ * Create EDEFProperties instance initialized with all the given properties
+ * @param properties must not be <code>null</code>
+ */
+ public EDEFProperties(Map<String, Object> properties) {
+ putAllEDEFProperties(properties);
+ }
+
+ @Override
+ public Object put(Object key, Object value) {
+ if (key instanceof String && value instanceof String) {
+ EDEFPropertiesValue oldValue = (EDEFPropertiesValue) this.get(key);
+ EDEFPropertiesValue newValue = new EDEFPropertiesValue((String) value);
+ return super.put(key, (oldValue == null) ? newValue : oldValue.addPropertyValue(newValue));
+ }
+ return super.put(key, value);
+ }
+
+ /**
+ * Put String->Object relation in as an EDEF property. Both the key and value
+ * must not be <code>null</code>. The value must be either a Set, List, Array
+ * type or a primitive type: Long/long, Byte/byte, Short/short, Int/Integer,
+ * char/Character, double/Double, float/Float. If array,set, or list, the
+ * elements must be one of the primitive types.
+ *
+ * @param key unique name/key for given value. Must not be <code>null</code>.
+ * @param value array,list,set of primitive type or instance of primitive type.
+ * @return existing Object with name/key. Null if no existing object exists.
+ */
+ public Object putEDEFProperty(String key, Object value) {
+ if (key != null && value != null) {
+ return super.put(key, new EDEFPropertiesValue(value));
+ }
+ return null;
+ }
+
+ /**
+ * Get EDEF properties as String -> Object map
+ *
+ * @return Map<String,Object> containing all name->EDEFPropertiesValue contents
+ * of this EDEFProperties
+ */
+ public Map<String, Object> getEDEFPropertiesAsMap() {
+ Map<String, Object> result = new TreeMap<String, Object>();
+ this.forEach((k, v) -> {
+ result.put((String) k, ((EDEFPropertiesValue) v).getValueObject());
+ });
+ return result;
+ }
+
+ private static void writeComments0(BufferedWriter bw, String comments) throws IOException {
+ bw.write("#"); //$NON-NLS-1$
+ int len = comments.length();
+ int current = 0;
+ int last = 0;
+ char[] uu = new char[6];
+ uu[0] = '\\';
+ uu[1] = 'u';
+ while (current < len) {
+ char c = comments.charAt(current);
+ if (c > '\u00ff' || c == '\n' || c == '\r') {
+ if (last != current)
+ bw.write(comments.substring(last, current));
+ if (c > '\u00ff') {
+ uu[2] = toHex((c >> 12) & 0xf);
+ uu[3] = toHex((c >> 8) & 0xf);
+ uu[4] = toHex((c >> 4) & 0xf);
+ uu[5] = toHex(c & 0xf);
+ bw.write(new String(uu));
+ } else {
+ bw.newLine();
+ if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') {
+ current++;
+ }
+ if (current == len - 1
+ || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!'))
+ bw.write("#"); //$NON-NLS-1$
+ }
+ last = current + 1;
+ }
+ current++;
+ }
+ if (last != current)
+ bw.write(comments.substring(last, current));
+ bw.newLine();
+ }
+
+ private static final char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
+ 'F' };
+
+ private static char toHex(int nibble) {
+ return hexDigit[(nibble & 0xF)];
+ }
+
+ /**
+ * Load EDEF properties from the given input stream
+ *
+ * @param ins InputStream to read the edef properties from. Must not be
+ * <code>null</code>
+ * @throws IOException if properties cannot be read from given InputStream
+ */
+ public synchronized void loadEDEFProperties(InputStream ins) throws IOException {
+ load(ins);
+ }
+
+ /**
+ * Load EDEF properties from the given reader
+ *
+ * @param reader Reader to read the edef properties from. Must not be
+ * <code>null</code>
+ * @throws IOException if properties cannot be read from given Reader
+ */
+ public void loadEDEFProperties(Reader reader) throws IOException {
+ load(reader);
+ }
+
+ /**
+ * Store EDEF properties to given Writer
+ *
+ * @param writer the Writer to write output to. Must not be <code>null</code>.
+ * @param comments
+ * @throws IOException
+ */
+ public void storeEDEFProperties(Writer writer, String comments) throws IOException {
+ storeEDEFProperties((writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer),
+ comments);
+ }
+
+ /**
+ * Store EDEF properties to the given buffered writer.
+ *
+ * @param bufferedWriter the BufferedWriter to write to. Must not be
+ * <code>null</code>
+ * @param comments Comment line prepended to properties file output. May
+ * be <code>null</code>.
+ * @throws IOException if properties cannot be written to bufferedWriter
+ */
+ public void storeEDEFProperties(BufferedWriter bufferedWriter, String comments) throws IOException {
+ if (comments != null) {
+ writeComments0(bufferedWriter, comments);
+ }
+ bufferedWriter.write("#" + new Date().toString()); //$NON-NLS-1$
+ bufferedWriter.newLine();
+ synchronized (this) {
+ for (Enumeration<?> e = keys(); e.hasMoreElements();) {
+ Object k = e.nextElement();
+ if (k instanceof String) {
+ String key = (String) k;
+ Object elemValue = get(key);
+ if (elemValue instanceof EDEFPropertiesValue) {
+ bufferedWriter.write(key + ((EDEFPropertiesValue) elemValue).getEDEFPropertyValueString());
+ bufferedWriter.newLine();
+ }
+ }
+ }
+ bufferedWriter.flush();
+ }
+ }
+
+ /**
+ * Put all the given properties into this map as EDEF properties, suitable for
+ * storing via {@link #storeEDEFProperties(Writer, String)}
+ *
+ * @param properties the properties to put. May not be <code>null</code>
+ */
+ public synchronized void putAllEDEFProperties(Map<String, Object> properties) {
+ properties.forEach((k, v) -> {
+ if (k instanceof String) {
+ putEDEFProperty((String) k, v);
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EndpointDescriptionLocator.java b/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EndpointDescriptionLocator.java
index 8cc2ea3..0b0b6b6 100644
--- a/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EndpointDescriptionLocator.java
+++ b/osgi/bundles/org.eclipse.ecf.osgi.services.remoteserviceadmin/src/org/eclipse/ecf/osgi/services/remoteserviceadmin/EndpointDescriptionLocator.java
@@ -15,10 +15,10 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
@@ -29,12 +29,12 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
-import java.util.UUID;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
@@ -1082,32 +1082,6 @@
/**
* @since 4.8
*/
- protected static class EDEFProperties extends Properties {
-
- private static final long serialVersionUID = -7351470248095230347L;
-
- @Override
- public synchronized Object put(Object key, Object value) {
- String keyStr = (String) key;
- String valueStr = (String) value;
- EDEFPropertyValue propValue = null;
- if (keyStr != null && valueStr != null) {
- EDEFPropertyValue newValue = new EDEFPropertyValue(valueStr);
- EDEFPropertyValue oldValue = (EDEFPropertyValue) this.get(keyStr);
- if (oldValue == null) {
- propValue = newValue;
- } else {
- propValue = oldValue.addPropertyValue(newValue);
- }
- super.put(keyStr, propValue);
- }
- return null;
- }
- }
-
- /**
- * @since 4.8
- */
protected EDEFProperties loadProperties(URL url) throws IOException {
EDEFProperties result = new EDEFProperties();
try (InputStream ins = url.openStream()) {
@@ -1119,324 +1093,83 @@
/**
* @since 4.8
*/
- public static class EDEFPropertyValue {
- private static int nextInt = 0;
- private static long nextLong = 0;
- private static short nextShort = 0;
- private static byte nextByte = 0;
- private String type1;
- private String type2;
- private String value;
- private Object resultValue;
- synchronized static int getNextInteger() {
- if (nextInt == Integer.MAX_VALUE) {
- nextInt = 0;
- }
- return ++nextInt;
- }
-
- boolean isArray() {
- return this.type1.equalsIgnoreCase("array"); //$NON-NLS-1$
- }
-
- boolean isSet() {
- return this.type1.equalsIgnoreCase("set"); //$NON-NLS-1$
- }
-
- boolean isList() {
- return this.type1.equalsIgnoreCase("list"); //$NON-NLS-1$
- }
-
- boolean isCollection() {
- return isSet() || isList();
- }
-
- boolean isSimpleType() {
- return !isSet() && !isList() && !isArray();
- }
-
- boolean hasTypeAgreement(EDEFPropertyValue otherValue) {
- String otherType = (otherValue.isArray() || otherValue.isCollection()) ? otherValue.type2
- : otherValue.type1;
- return (isArray() || isCollection()) ? this.type2.equalsIgnoreCase(otherType)
- : this.type1.equalsIgnoreCase(otherType);
- }
-
- EDEFPropertyValue addPropertyValue(EDEFPropertyValue newValue) {
- if (!hasTypeAgreement(newValue)) {
- LogUtility.logError("addEDEFPropertyValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
- "type disagreement between property values for old type1=" + this.type1 + ",type2=" + this.type2 //$NON-NLS-1$ //$NON-NLS-2$
- + ", and new type1=" + newValue.type1 + ",type2=" + newValue.type2); //$NON-NLS-1$ //$NON-NLS-2$
- return this;
- }
- // get old and new values
- Object ov = this.getValue();
- Object nv = newValue.getValue();
- // If we've got a collection already
- if (isCollection()) {
- Collection oldVs = (Collection) ov;
- // If newValue is also collection
- if (newValue.isCollection()) {
- // Then addAll
- oldVs.addAll((Collection) nv);
- } else if (newValue.isSimpleType()) {
- // Add single element
- oldVs.add(nv);
- }
- } else if (isArray()) {
- // Get old length
- int oldLength = Array.getLength(ov);
- Object newResultValue = null;
- if (newValue.isArray()) {
- // new value is also array...get length
- int newLength = Array.getLength(nv);
- // Check to make sure that the type of elements is same (type2 for arrays)
- newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + newLength);
- // copy ov contents to newResultValue
- System.arraycopy(ov, 0, newResultValue, 0, oldLength);
- // append nv contents to newResultValue after ov contents
- System.arraycopy(nv, 0, newResultValue, oldLength, newLength);
- } else if (newValue.isSimpleType()) {
- newResultValue = Array.newInstance(ov.getClass().getComponentType(), oldLength + 1);
- System.arraycopy(ov, 0, newResultValue, 0, oldLength);
- Array.set(newResultValue, oldLength, nv);
- }
- if (newResultValue != null) {
- this.resultValue = newResultValue;
- }
- }
- return this;
- }
-
- synchronized static long getNextLong() {
- if (nextLong == Long.MAX_VALUE) {
- nextLong = 0;
- }
- return ++nextLong;
- }
-
- synchronized static short getNextShort() {
- if (nextShort == Short.MAX_VALUE) {
- nextShort = 0;
- }
- return ++nextShort;
- }
-
- synchronized static byte getNextByte() {
- if (nextByte == Byte.MAX_VALUE) {
- nextByte = 0;
- }
- return ++nextByte;
- }
-
- public EDEFPropertyValue(String value) {
- // Default type1 is String
- this.type1 = "String"; //$NON-NLS-1$
- // Default type2 is String also
- this.type2 = "String"; //$NON-NLS-1$
- // Split value with =
- String[] valueArr = value.split("="); //$NON-NLS-1$
- // Split first one
- if (valueArr.length > 1) {
- // split second element in valueArr by :
- String[] firstSplit = valueArr[0].split(":"); //$NON-NLS-1$
- // If more than one then type2 is second element in firstSplit
- if (firstSplit.length > 1) {
- this.type2 = firstSplit[1];
- }
- // In either case type1 is firstSplit[0]
- this.type1 = firstSplit[0];
- }
- // Now set value to the last elemtn in the valueArr
- this.value = valueArr[valueArr.length - 1];
- }
-
- private Object getSimpleValue(Class<?> simpleType, Object value) {
- try {
- return simpleType.getDeclaredMethod("valueOf", new Class[] { String.class }).invoke(null, value); //$NON-NLS-1$
- } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
- | NoSuchMethodException | SecurityException e) {
- LogUtility.logWarning("getSimpleValue", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, this.getClass(), //$NON-NLS-1$
- "Cannot create instance of simpleType=" + simpleType + ", value=" + value); //$NON-NLS-1$ //$NON-NLS-2$
- return null;
- }
- }
-
- boolean isUnique() {
- return "unique".equals(this.type2); //$NON-NLS-1$
- }
-
- Object getSimpleValue(String simpleType, String value) {
- switch (simpleType) {
- case "long": //$NON-NLS-1$
- case "Long": //$NON-NLS-1$
- if ("unique".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
- return getNextLong();
- } else if ("nanoTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
- return System.nanoTime();
- } else if ("milliTime".equalsIgnoreCase(this.type2)) { //$NON-NLS-1$
- return System.currentTimeMillis();
- }
- return getSimpleValue(Long.class, value);
- case "double": //$NON-NLS-1$
- case "Double": //$NON-NLS-1$
- return getSimpleValue(Double.class, value);
- case "float": //$NON-NLS-1$
- case "Float": //$NON-NLS-1$
- return getSimpleValue(Float.class, value);
- case "int": //$NON-NLS-1$
- case "Integer": //$NON-NLS-1$
- if ("unique".equals(this.type2)) { //$NON-NLS-1$
- return getNextInteger();
- }
- return getSimpleValue(Integer.class, value);
- case "Byte": //$NON-NLS-1$
- case "byte": //$NON-NLS-1$
- if ("unique".equals(this.type2)) { //$NON-NLS-1$
- return getNextByte();
- }
- return getSimpleValue(Byte.class, value);
- case "char": //$NON-NLS-1$
- case "Character": //$NON-NLS-1$
- return getSimpleValue(Character.class, value.toCharArray()[0]);
- case "boolean": //$NON-NLS-1$
- case "Boolean": //$NON-NLS-1$
- return getSimpleValue(Boolean.class, value);
- case "short": //$NON-NLS-1$
- case "Short": //$NON-NLS-1$
- if ("unique".equals(this.type2)) { //$NON-NLS-1$
- return getNextShort();
- }
- return getSimpleValue(Short.class, value);
- case "uuid": //$NON-NLS-1$
- case "Uuid": //$NON-NLS-1$
- case "UUID": //$NON-NLS-1$
- // we don't care whether 'unique' is given or not
- return UUID.randomUUID().toString();
- case "String": //$NON-NLS-1$
- case "string": //$NON-NLS-1$
- return value;
- default:
- return null;
- }
- }
-
- Object getArrayValues(String collectionValue) {
- String[] elements = this.value.split("\\s*,\\s*"); //$NON-NLS-1$
- Object result = null;
- for (int i = 0; i < elements.length; i++) {
- Object elementValue = getSimpleValue(this.type2, elements[i]);
- if (elementValue == null) {
- LogUtility.logWarning("getArrayValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
- "array element=" + elements[i] + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$
- continue;
- } else {
- if (i == 0) {
- result = Array.newInstance(elementValue.getClass(), elements.length);
- }
- Array.set(result, i, elementValue);
- }
- }
- return result;
- }
-
- Collection<Object> getCollectionValues(Collection<Object> c, String collectionValue) {
- String[] elements = this.value.split("\\s*,\\s*"); //$NON-NLS-1$
- for (String element : elements) {
- Object elementValue = getSimpleValue(this.type2, element);
- if (elementValue == null) {
- LogUtility.logWarning("getCollectionValues", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
- "array element=" + element + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$
- continue;
- } else {
- c.add(elementValue);
- }
- }
- return c;
- }
-
- public synchronized Object getValue() {
- if (this.resultValue == null) {
- switch (this.type1) {
- case "array": //$NON-NLS-1$
- this.resultValue = getArrayValues(this.value);
- break;
- case "list": //$NON-NLS-1$
- this.resultValue = getCollectionValues(new ArrayList(), this.value);
- break;
- case "set": //$NON-NLS-1$
- this.resultValue = getCollectionValues(new HashSet(), this.value);
- break;
- default:
- this.resultValue = getSimpleValue(this.type1, this.value);
- break;
- }
- }
- return this.resultValue;
- }
+ /**
+ * @since 4.8
+ */
+ protected Map<String, Object> processProperties(EDEFProperties props) {
+ return props.getEDEFPropertiesAsMap();
}
/**
* @since 4.8
*/
- protected Map<String, Object> processProperties(Properties props) {
- Map<String, Object> result = new HashMap<String, Object>();
- if (props == null) {
- return result;
+ protected Map<String, Object> loadDefaultProperties(Map<String, Object> props, URL url) {
+ try {
+ props = PropertiesUtil.mergePropertiesRaw(props, processProperties(loadProperties(url)));
+ trace("loadDefaultProperties", "loaded default.properties file=" + url.getFile() //$NON-NLS-1$ //$NON-NLS-2$
+ + " loaded properties=" //$NON-NLS-1$
+ + props);
+ } catch (IOException e) {
+ LogUtility.logWarning("findOverrideProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
+ "Could not load default properties=" + url); //$NON-NLS-1$
}
- props.forEach((k, v) -> {
- String name = (String) k;
- Object value = ((EDEFPropertyValue) v).getValue();
- if (value != null) {
- result.put(name, value);
- } else {
- LogUtility.logWarning("processProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
- "Invalid EDEFPropertyValue for name=" + name + ". Not added to processed properties"); //$NON-NLS-1$ //$NON-NLS-2$
+ return props;
+ }
+
+ /**
+ * @since 4.8
+ */
+ protected Map<String, Object> loadAllDefaultProperties(URL url) {
+ Map<String, Object> props = new TreeMap<String, Object>();
+ URL rootUrl = null;
+ try {
+ rootUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), "/"); //$NON-NLS-1$
+ } catch (MalformedURLException e) {
+ LogUtility.logError("loadAllDefaultProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
+ "MalformedUrlException creating rootUrl from url=" + url); //$NON-NLS-1$
+ return props;
+ }
+ String pathSegment = ""; //$NON-NLS-1$
+ Iterator<Path> pathIterator = Paths.get(url.getPath()).iterator();
+ do {
+ String newPath = pathSegment + "/" + DEFAULT_PROPERTIES_FILE; //$NON-NLS-1$
+ try {
+ props = loadDefaultProperties(props, new URL(rootUrl, newPath));
+ } catch (MalformedURLException e) {
+ LogUtility.logError("loadAllDefaultProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
+ "MalformedUrlException creating rootUrl from url=" + url); //$NON-NLS-1$
+ return props;
}
- });
- return result;
+ pathSegment = pathSegment + "/" + pathIterator.next(); //$NON-NLS-1$
+ } while (pathIterator.hasNext());
+ return props;
}
/**
* @since 4.7
*/
protected Map<String, Object> findOverrideProperties(Bundle bundle, URL fileURL) {
- Map<String, Object> mergedProps = new HashMap<String, Object>();
- URL defaultPropsFileURL = getDefaultPropsURLFromEDFileURL(fileURL);
- if (defaultPropsFileURL != null) {
- trace("handleEndpointDescriptionFile", //$NON-NLS-1$
- "Attempting to load default.properties. BundleId=" + bundle.getBundleId() + " defaultPropsFileURL=" //$NON-NLS-1$ //$NON-NLS-2$
- + defaultPropsFileURL);
- try {
- mergedProps = PropertiesUtil.mergePropertiesRaw(mergedProps,
- processProperties(loadProperties(defaultPropsFileURL)));
- trace("findOverrideProperties", "loaded default.properties file=" + defaultPropsFileURL.getFile() //$NON-NLS-1$ //$NON-NLS-2$
- + " properties loaded=" //$NON-NLS-1$
- + mergedProps);
- } catch (IOException e) {
- LogUtility.logWarning("findOverrideProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
- "Could not load default properties file=" + defaultPropsFileURL + ",edef fileUrl=" //$NON-NLS-1$ //$NON-NLS-2$
- + fileURL.getFile());
- }
- }
+ Map<String, Object> defaultProperties = loadAllDefaultProperties(fileURL);
+ trace("findOverrideProperties", "merged default.properties=" + defaultProperties); //$NON-NLS-1$ //$NON-NLS-2$
+ Map<String, Object> overrideProps = new HashMap<String, Object>();
URL propsFileURL = getPropsURLFromEDFileURL(fileURL);
if (propsFileURL != null) {
trace("handleEndpointDescriptionFile", //$NON-NLS-1$
"Attemping to load <file>.properties. BundleId=" + bundle.getBundleId() + " propsFileURL=" //$NON-NLS-1$ //$NON-NLS-2$
+ propsFileURL);
try {
- mergedProps = PropertiesUtil.mergePropertiesRaw(mergedProps,
+ overrideProps = PropertiesUtil.mergePropertiesRaw(defaultProperties,
processProperties(loadProperties(propsFileURL)));
trace("findOverrideProperties", //$NON-NLS-1$
"loaded override properties file=" + fileURL.getFile() + " merged Properties=" //$NON-NLS-1$ //$NON-NLS-2$
- + mergedProps);
+ + overrideProps);
} catch (IOException e) {
LogUtility.logWarning("findOverrideProperties", DebugOptions.ENDPOINT_DESCRIPTION_LOCATOR, getClass(), //$NON-NLS-1$
"Could not load properties fileUrl=" + propsFileURL + ",fileUrl=" + fileURL.getFile()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
- return (!mergedProps.isEmpty()) ? mergedProps : null;
+ return (!overrideProps.isEmpty()) ? overrideProps : null;
}
EndpointDescription findED(IServiceID serviceID) {