ENTAXY-480 release version 1.8.3

This commit is contained in:
2023-08-03 04:45:45 +03:00
parent 5844a2e5cf
commit 3cc15f7459
236 changed files with 21106 additions and 0 deletions

View File

@ -0,0 +1,336 @@
/*-
* ~~~~~~licensing~~~~~~
* object-factory
* ==========
* Copyright (C) 2020 - 2023 EmDev LLC
* ==========
* Licensed 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.
* ~~~~~~/licensing~~~~~~
*/
package ru.entaxy.platform.base.objects.factory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.CONFIGURATION.DIRECTIVES;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.CONFIGURATION.DIRECTIVES.OVERRIDE_MODE;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.FieldInfo;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.OutputInfo;
import ru.entaxy.platform.base.objects.factory.Importer.ImportInfo;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;
public class EntaxyFactoryUtils {
public static final String PROP_HIERARCHY = "hierarchy";
public static Gson gson = new Gson();
public static String getEffectiveJson(EntaxyFactory factory) {
JsonObject result = new JsonObject();
// factory
JsonObject factoryData = new JsonObject();
factoryData.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.ID, factory.getId());
factoryData.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.TYPE, factory.getType());
factoryData.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.CATEGORY, factory.getCategory());
factoryData.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.LABEL, factory.getLabel());
factoryData.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.DESCRIPTION, factory.getDescription());
result.add(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME, factoryData);
// typed data
String type = factory.getType();
JsonElement typeData = gson.toJsonTree(factory.getTypeInfo());
result.add(type, typeData);
// fields
JsonObject fieldsData = new JsonObject();
for (FieldInfo fi: factory.getFields()) {
fieldsData.add(fi.getName(), fi.getJsonOrigin());
}
result.add(EntaxyFactory.CONFIGURATION.FIELDS_SECTION_NAME, fieldsData);
// outputs
JsonObject outputsData = new JsonObject();
for (OutputInfo oi: factory.getOutputs()) {
JsonObject outputData = new JsonObject();
outputData.addProperty(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.IS_DEFAULT, oi.isDefault());
outputData.addProperty(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.GENERATOR, oi.getGenerator());
outputData.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.SCOPES
, gson.toJsonTree(oi.getScopes()));
if (oi.getConfig() != null)
outputData.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.CONFIG
, gson.toJsonTree(oi.getConfig()));
JsonObject outputFields = new JsonObject();
for (FieldInfo fi: oi.getFields()) {
outputFields.add(fi.getName(), fi.getJsonOrigin());
}
outputData.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.FIELDS, outputFields);
outputsData.add(oi.getType(), outputData);
}
result.add(EntaxyFactory.CONFIGURATION.OUTPUTS_SECTION_NAME, outputsData);
return result.toString();
}
public static JsonObject cleanJson(JsonObject origin) {
JsonObject result = origin.deepCopy();
cleanObject(result, EntaxyFactory.CONFIGURATION.DIRECTIVES.getAllDirectives());
return result;
}
public static void cleanObject(JsonObject origin, Set<String> toRemove) {
for (String name: toRemove)
origin.remove(name);
for (Entry<String, JsonElement> entry: origin.entrySet())
if (entry.getValue().isJsonObject())
cleanObject(entry.getValue().getAsJsonObject(), toRemove);
}
public static interface FactoryConfigurationStorage {
JsonObject getConfiguration(String factoryId);
}
public static JsonObject calculateEffectiveJson(JsonObject origin, FactoryConfigurationStorage storage) {
JsonObject originCopy = origin.deepCopy();
JsonObject result = originCopy;
JsonObject error = new JsonObject();
JsonObject errorData = new JsonObject();
JsonArray errorReqs = new JsonArray();
error.add("#CALC_ERROR", errorData);
errorData.add("#REQS", errorReqs);
// process imports in originCopy
Importer importer = new Importer(originCopy);
List<ImportInfo> imports = importer.findImports();
if (!imports.isEmpty()) {
final Map<String, JsonObject> factoryJsonMap = new HashMap<>();
imports.stream()
.forEach(imp->
imp.importDescriptors.stream()
.filter(id->CommonUtils.isValid(id.factoryId))
.forEach(id->factoryJsonMap.put(id.factoryId, null))
);
boolean inconsistent = false;
for (String factoryId: factoryJsonMap.keySet()) {
JsonObject factoryJson;
if ("#".equals(factoryId))
factoryJson = origin;
else
factoryJson = storage.getConfiguration(factoryId);
if (factoryJson == null) {
inconsistent = true;
errorReqs.add(factoryId);
} else {
factoryJsonMap.put(factoryId, factoryJson);
}
}
if (inconsistent)
return error;
importer.addImportedSources(factoryJsonMap);
importer.processImports(imports);
}
// process inheritance
String parent = getParentFromJson(origin);
if (CommonUtils.isValid(parent)) {
JsonObject parentObject = storage.getConfiguration(parent);
if (parentObject == null) {
errorReqs.add(parent);
return error;
}
result = parentObject.deepCopy();
prepareTypeInfo(result, originCopy);
processObjectOverriding(result, originCopy, OVERRIDE_MODE.UPDATE);
} else {
result = originCopy;
}
return result;
}
public static void prepareTypeInfo(JsonObject parent, JsonObject child) {
JsonElement parentTypeE = JSONUtils.findElement(parent, "factory.type");
JsonElement childTypeE = JSONUtils.findElement(child, "factory.type");
String parentType = "";
String childType = "";
if ((parentTypeE!=null) && parentTypeE.isJsonPrimitive())
parentType = parentTypeE.getAsString();
if ((childTypeE!=null) && childTypeE.isJsonPrimitive())
childType = childTypeE.getAsString();
JsonObject parentProperties = getTypeProperties(parent);
JsonObject childProperties = getTypeProperties(child);
if (childProperties==null)
return;
if (!parentType.equals(childType) && CommonUtils.isValid(childType)) {
// copy parent type info with child type
parent.add(childType, getTypeProperties(parent).deepCopy());
}
JsonElement parentHierarchy = JSONUtils.findElement(parentProperties, PROP_HIERARCHY);
JsonArray childHierarchy = new JsonArray();
if ((parentHierarchy != null) && parentHierarchy.isJsonArray()) {
childHierarchy.addAll(parentHierarchy.getAsJsonArray());
}
childHierarchy.add(JSONUtils.findElement(parent, "factory.id"));
childProperties.add(PROP_HIERARCHY, childHierarchy);
}
public static JsonObject resolveVariants(JsonObject root) {
JsonObject result = root.deepCopy();
processObjectVariants(result, root);
return result;
}
public static void processObjectVariants(JsonObject currentObject, JsonObject root) {
JsonObject variants = getVariants(currentObject);
if (variants != null) {
String property = variants.has("property")
?variants.get("property").getAsString()
:"";
if (CommonUtils.isValid(property)) {
JsonObject values = (variants.has("values") && variants.get("values").isJsonObject())
?variants.get("values").getAsJsonObject()
:null;
if (values != null) {
JsonObject typeProperties = getTypeProperties(root);
JsonElement je = JSONUtils.findElement(typeProperties, property);
if (je != null) {
String value = je.getAsString();
if (values.has(value) && values.get(value).isJsonObject()) {
JsonObject variant = values.get(value).getAsJsonObject();
for (Entry<String, JsonElement> entry: variant.entrySet()) {
currentObject.remove(entry.getKey());
currentObject.add(entry.getKey(), entry.getValue().deepCopy());
}
}
}
}
}
currentObject.remove(DIRECTIVES.VARIANTS);
}
for (Entry<String, JsonElement> entry: currentObject.entrySet()) {
if (entry.getValue().isJsonObject())
processObjectVariants(entry.getValue().getAsJsonObject(), root);
}
}
public static JsonObject getVariants(JsonObject currentObject) {
if (currentObject.has(DIRECTIVES.VARIANTS) && currentObject.get(DIRECTIVES.VARIANTS).isJsonObject())
return currentObject.get(DIRECTIVES.VARIANTS).getAsJsonObject();
return null;
}
public static JsonObject getTypeProperties(JsonObject currentObject) {
JsonElement type = JSONUtils.findElement(currentObject, "factory.type");
if (type == null)
return new JsonObject();
String typeValue = type.getAsString();
if (!CommonUtils.isValid(typeValue))
return new JsonObject();
JsonElement typeProperties = JSONUtils.findElement(currentObject, typeValue);
if (typeProperties.isJsonObject())
return typeProperties.getAsJsonObject();
return new JsonObject();
}
public static String getParentFromJson(JsonObject jsonObject) {
if (!jsonObject.has(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME))
return null;
JsonObject fs = jsonObject.get(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME).getAsJsonObject();
if (fs.has(EntaxyFactory.CONFIGURATION.FACTORY.PARENT))
return fs.get(EntaxyFactory.CONFIGURATION.FACTORY.PARENT).getAsString();
return null;
}
public static void processObjectOverriding(JsonObject currentObject, JsonObject newObject, OVERRIDE_MODE defaultMode) {
OVERRIDE_MODE mode = getOverrideMode(currentObject, defaultMode);
if (OVERRIDE_MODE.IGNORE.equals(mode))
return;
if (OVERRIDE_MODE.REPLACE.equals(mode)) {
Set<String> set = new HashSet<String>(currentObject.keySet());
for (String entry: set)
currentObject.remove(entry);
for (Entry<String, JsonElement> entry: newObject.entrySet())
currentObject.add(entry.getKey(), entry.getValue().deepCopy());
return;
}
if (OVERRIDE_MODE.APPEND.equals(mode)) {
for (Entry<String, JsonElement> entry: newObject.entrySet())
if (!currentObject.has(entry.getKey()))
currentObject.add(entry.getKey(), entry.getValue().deepCopy());
return;
}
if (OVERRIDE_MODE.UPDATE.equals(mode)) {
// update existing
Set<String> set = new HashSet<String>(currentObject.keySet());
for (String key: set) {
if (newObject.has(key)) {
JsonElement currentElement = currentObject.get(key);
JsonElement newElement = newObject.get(key);
if (currentElement.isJsonObject() && newElement.isJsonObject()) {
processObjectOverriding(currentElement.getAsJsonObject(), newElement.getAsJsonObject(), mode);
} else {
currentObject.remove(key);
currentObject.add(key, newElement.deepCopy());
}
}
}
// add new
for (Entry<String, JsonElement> entry: newObject.entrySet())
if (!currentObject.has(entry.getKey()))
currentObject.add(entry.getKey(), entry.getValue().deepCopy());
}
}
public static EntaxyFactory.CONFIGURATION.DIRECTIVES.OVERRIDE_MODE getOverrideMode(JsonObject object, OVERRIDE_MODE defaultMode) {
OVERRIDE_MODE result = defaultMode;
if (object.has(EntaxyFactory.CONFIGURATION.DIRECTIVES.OVERRIDE)) {
try {
OVERRIDE_MODE mode = OVERRIDE_MODE.valueOfLabel(object.get(EntaxyFactory.CONFIGURATION.DIRECTIVES.OVERRIDE).getAsString());
if (mode != null)
result = mode;
} catch (Exception e) {
}
}
return result;
}
}

View File

@ -0,0 +1,382 @@
/*-
* ~~~~~~licensing~~~~~~
* object-factory
* ==========
* Copyright (C) 2020 - 2023 EmDev LLC
* ==========
* Licensed 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.
* ~~~~~~/licensing~~~~~~
*/
package ru.entaxy.platform.base.objects.factory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.CONFIGURATION.DIRECTIVES.OVERRIDE_MODE;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;
public class Importer {
protected JsonObject origin;
protected Map<String, JsonObject> importedSources = new HashMap<>();
public Importer(JsonObject origin) {
this.origin = origin;
}
public List<ImportInfo> findImports(){
return findImports(this.origin);
}
public List<ImportInfo> findImports(JsonObject object) {
List<ImportInfo> result = new ArrayList<>();
if (object.has(EntaxyFactory.CONFIGURATION.DIRECTIVES.IMPORT))
result.add(new ImportInfo(object));
for (Entry<String, JsonElement> entry: object.entrySet())
if (entry.getValue().isJsonObject())
result.addAll(findImports(entry.getValue().getAsJsonObject()));
return result;
}
public void processImports(List<ImportInfo> imports) {
for (ImportInfo ii: imports) {
JsonObject newData = new JsonObject();
for (ImportDescriptor id: ii.importDescriptors) {
JsonObject imported = importedSources.get(id.factoryId);
if (imported == null)
continue;
JsonElement je = JSONUtils.findElement(imported, id.location);
if ((je==null) || !je.isJsonObject())
continue;
Filter f;
if (id.origin.has("filter"))
f = CompositeFilter.create(id.origin.get("filter"));
else
f = new NullFilter();
List<Entry<String, JsonElement>> filteredList = je.getAsJsonObject().entrySet()
.stream()
.filter(e->f.isAccepted(e.getKey(), e.getValue()))
.collect(Collectors.toList());
JsonObject idResult = new JsonObject();
for (Entry<String, JsonElement> entry: filteredList)
idResult.add(id.prefix + entry.getKey(), entry.getValue());
EntaxyFactoryUtils.processObjectOverriding(
newData
, idResult
, OVERRIDE_MODE.UPDATE);
}
JsonObject current = ii.owner.deepCopy();
current.remove(EntaxyFactory.CONFIGURATION.DIRECTIVES.IMPORT);
EntaxyFactoryUtils.processObjectOverriding(newData, current, OVERRIDE_MODE.UPDATE);
Set<String> keys = new HashSet<>(ii.owner.keySet());
for (String key: keys)
ii.owner.remove(key);
for (Entry<String, JsonElement> entry: newData.entrySet())
ii.owner.add(entry.getKey(), entry.getValue());
}
}
// INTERNAL CLASSES
public static abstract class Filter {
public static final String ATTRIBUTE_KEY = "$key";
public static final String ATTRIBUTE_VALUE = "$value";
abstract public boolean isAccepted(String key, JsonElement value);
}
public static class CompositeFilter extends Filter {
List<Filter> filters = new ArrayList<>();
@Override
public boolean isAccepted(String key, JsonElement value) {
for (Filter f: filters)
if (!f.isAccepted(key, value))
return false;
return true;
}
public static Filter create(JsonElement object) {
if (!object.isJsonObject())
return new NullFilter();
final CompositeFilter filter = new CompositeFilter();
if (object.getAsJsonObject().has(ContainsFilter.FILTER_NAME)) {
JsonElement je = object.getAsJsonObject().get(ContainsFilter.FILTER_NAME);
if (je.isJsonArray())
je.getAsJsonArray().forEach(e->filter.filters.add(new ContainsFilter(e)));
}
if (object.getAsJsonObject().has(ContainedFilter.FILTER_NAME)) {
JsonElement je = object.getAsJsonObject().get(ContainedFilter.FILTER_NAME);
if (je.isJsonArray())
je.getAsJsonArray().forEach(e->filter.filters.add(new ContainedFilter(e)));
}
return filter;
}
}
public static class NullFilter extends Filter {
@Override
public boolean isAccepted(String key, JsonElement value) {
return true;
}
}
public static abstract class CommonFilter extends Filter {
String attribute = null;
List<String> values = new ArrayList<>();
boolean isInversed = false;
protected CommonFilter(JsonElement element) {
if (!element.isJsonObject())
return;
JsonObject jo = element.getAsJsonObject();
if (jo.has("inverse"))
this.isInversed = jo.get("inverse").getAsBoolean();
}
@Override
public final boolean isAccepted(String key, JsonElement value) {
if (isInversed)
return !calculateAccepted(key, value);
else
return calculateAccepted(key, value);
}
protected abstract boolean calculateAccepted(String key, JsonElement value);
}
public static class ContainedFilter extends CommonFilter {
public static final String FILTER_NAME = "contained";
public ContainedFilter(JsonElement element) {
super(element);
if (!element.isJsonObject())
return;
JsonObject jo = element.getAsJsonObject();
if (jo.has("attribute"))
this.attribute = jo.get("attribute").getAsString();
if (jo.has("values")) {
if (jo.get("values").isJsonPrimitive())
values.add(jo.get("values").getAsString());
if (jo.get("values").isJsonArray())
jo.get("values").getAsJsonArray().forEach(v->values.add(v.getAsString()));
}
}
@Override
protected boolean calculateAccepted(String key, JsonElement value) {
if (values.isEmpty())
return true;
String testValue = "";
if (ATTRIBUTE_KEY.equals(attribute))
testValue = key;
else if (ATTRIBUTE_VALUE.equals(attribute))
testValue = value.getAsString();
else {
if (value.isJsonObject()) {
JsonObject jo = value.getAsJsonObject();
if (jo.has(attribute))
testValue = jo.get(attribute).getAsString();
}
}
if (!CommonUtils.isValid(testValue))
return false;
return values.contains(testValue);
}
}
public static class ContainsFilter extends CommonFilter {
public static final String FILTER_NAME = "contains";
String separator = ",";
boolean containsAll = false;
public ContainsFilter(JsonElement element) {
super(element);
if (!element.isJsonObject())
return;
JsonObject jo = element.getAsJsonObject();
if (jo.has("attribute"))
this.attribute = jo.get("attribute").getAsString();
if (jo.has("separator"))
this.separator = jo.get("separator").getAsString();
if (jo.has("containsAll"))
this.containsAll = jo.get("containsAll").getAsBoolean();
if (jo.has("values")) {
if (jo.get("values").isJsonPrimitive())
values.add(jo.get("values").getAsString());
if (jo.get("values").isJsonArray())
jo.get("values").getAsJsonArray().forEach(v->values.add(v.getAsString()));
}
}
@Override
protected boolean calculateAccepted(String key, JsonElement value) {
if (values.isEmpty())
return true;
String testValue = "";
List<String> vals = new ArrayList<>();
if (ATTRIBUTE_KEY.equals(attribute))
testValue = key;
else if (ATTRIBUTE_VALUE.equals(attribute))
testValue = value.getAsString();
else {
if (value.isJsonObject()) {
JsonObject jo = value.getAsJsonObject();
if (jo.has(attribute)) {
JsonElement je = jo.get(attribute);
if (je.isJsonPrimitive()) {
testValue = jo.get(attribute).getAsString();
if (!CommonUtils.isValid(testValue))
return false;
String[] arr = testValue.split(separator);
vals = Arrays.asList(arr).stream().map(s->s.trim()).collect(Collectors.toList());
} else if (je.isJsonArray()) {
JsonArray ja = je.getAsJsonArray();
for (int i=0; i<ja.size(); i++) {
if (ja.get(i) == null)
continue;
if (ja.get(i).isJsonPrimitive())
vals.add(ja.get(i).getAsString());
else
vals.add(ja.get(i).toString());
}
}
}
}
}
if (containsAll)
return vals.containsAll(values);
for (String v: values)
if (vals.contains(v))
return true;
return false;
}
}
public static class ImportInfo {
public JsonObject owner;
public JsonElement importElement;
public List<ImportDescriptor> importDescriptors = new ArrayList<>();
public ImportInfo(JsonObject owner) {
List<JsonObject> foundImports = new ArrayList<>();
this.owner = owner;
this.importElement = owner.get(EntaxyFactory.CONFIGURATION.DIRECTIVES.IMPORT);
if (this.importElement.isJsonObject()) {
foundImports.add(this.importElement.getAsJsonObject());
}
if (this.importElement.isJsonArray()) {
JsonArray ja = this.importElement.getAsJsonArray();
for (int i=0; i<ja.size(); i++) {
JsonElement je = ja.get(i);
if (je.isJsonObject())
foundImports.add(je.getAsJsonObject());
}
}
for (JsonObject jo: foundImports)
this.importDescriptors.add(new ImportDescriptor(jo));
}
}
public static class ImportDescriptor {
public String factoryId;
public String location = "";
public String prefix = "";
public JsonObject origin;
public ImportDescriptor (JsonObject origin) {
this.origin = origin;
if (this.origin.has("sourceFactoryId"))
this.factoryId = this.origin.get("sourceFactoryId").getAsString();
if (this.origin.has("location"))
this.location = this.origin.get("location").getAsString();
if (this.origin.has("prefix"))
this.prefix = this.origin.get("prefix").getAsString();
}
}
// S & G
public Map<String, JsonObject> getImportedSources() {
return importedSources;
}
public void addImportedSources(Map<String, JsonObject> importedSources) {
this.importedSources.putAll(importedSources);
}
public void setImportedSources(Map<String, JsonObject> importedSources) {
this.importedSources = importedSources;
}
public JsonObject getOrigin() {
return origin;
}
}

View File

@ -0,0 +1,38 @@
/*-
* ~~~~~~licensing~~~~~~
* object-factory
* ==========
* Copyright (C) 2020 - 2023 EmDev LLC
* ==========
* Licensed 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.
* ~~~~~~/licensing~~~~~~
*/
package ru.entaxy.platform.base.objects.factory.exceptions;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryException;
@SuppressWarnings("serial")
public class NotSupportedForAbstractFactory extends EntaxyFactoryException {
private static String getMessage(EntaxyFactory factory, String operation) {
return String.format("Factory [%s] is abstract, operation [%s] not supported"
, factory.getId()
, operation);
}
public NotSupportedForAbstractFactory(EntaxyFactory factory, String operation) {
super(getMessage(factory, operation));
}
}

View File

@ -0,0 +1,54 @@
/*-
* ~~~~~~licensing~~~~~~
* object-factory
* ==========
* Copyright (C) 2020 - 2023 EmDev LLC
* ==========
* Licensed 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.
* ~~~~~~/licensing~~~~~~
*/
package ru.entaxy.platform.base.objects.factory.impl;
import java.util.HashMap;
import java.util.Map;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
@Component (service = FactoryRegistry.class, immediate = true)
public class FactoryRegistry {
protected Map<String, String> inheritance = new HashMap<>();
protected Map<String, EntaxyFactory> factories = new HashMap<>();
@Reference (unbind = "removeFactory", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC
, policyOption = ReferencePolicyOption.GREEDY)
public void addFactory(EntaxyFactory entaxyFactory) {
this.inheritance.put(entaxyFactory.getId(), entaxyFactory.getParent());
this.factories.put(entaxyFactory.getId(), entaxyFactory);
}
public void removeFactory(EntaxyFactory entaxyFactory) {
this.inheritance.remove(entaxyFactory.getId());
this.factories.remove(entaxyFactory.getId());
}
public String getParentFactory(String factoryId) {
return this.inheritance.get(factoryId);
}
}

View File

@ -0,0 +1,448 @@
/*-
* ~~~~~~licensing~~~~~~
* object-factory
* ==========
* Copyright (C) 2020 - 2023 EmDev LLC
* ==========
* Licensed 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.
* ~~~~~~/licensing~~~~~~
*/
package ru.entaxy.platform.base.objects.factory.tracker;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryUtils;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryUtils.FactoryConfigurationStorage;
import ru.entaxy.platform.base.objects.factory.impl.DefaultFactory;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;
import ru.entaxy.platform.base.support.osgi.tracker.BundleTrackerCustomizerListener;
@Component (service = TrackedFactoryManager.class, immediate = true)
public class TrackedFactoryManager implements BundleTrackerCustomizerListener<List<TrackedFactory>>, FactoryConfigurationStorage {
private static final Logger log = LoggerFactory.getLogger(TrackedFactoryManager.class);
public static final String DEFAULT_PARENT = "base-object";
protected BundleContext bundleContext;
protected Map<String, TrackedManagedFactory> managedFactories = new HashMap<>();
@Activate
public void activate(ComponentContext componentContext) {
this.bundleContext = componentContext.getBundleContext();
}
@Override
public void added(List<TrackedFactory> managedObject) {
if (managedObject == null) {
log.debug("managedObject is null");
return;
}
// List<TrackedManagedFactory> processedFactories = new ArrayList<>();
FactoryProcessor factoryProcessor = new FactoryProcessor();
for (TrackedFactory tf: managedObject) {
log.info("Added factory: " + tf.getId());
TrackedManagedFactory tmf = new TrackedManagedFactory(tf);
// if we already have factory with the same id
if (managedFactories.containsKey(tmf.factoryId)) {
tmf = managedFactories.get(tmf.factoryId);
// remove old service
tmf.deactivate();
tmf.reload(tf);
tmf.detachParent();
tmf.detachRequirements();
} else {
this.managedFactories.put(tmf.factoryId, tmf);
}
if (!CommonUtils.isValid(tmf.parent) && !DEFAULT_PARENT.equalsIgnoreCase(tmf.factoryId)) {
tmf.setParent(DEFAULT_PARENT);
}
if (CommonUtils.isValid(tmf.parent)) {
TrackedManagedFactory parentF = managedFactories.get(tmf.parent);
if ((parentF != null) && parentF.isActive) {
tmf.attachParent(parentF);
} else {
tmf.addWaiting(tmf.parent);
}
}
for (String requirement: tmf.requirements) {
TrackedManagedFactory req = managedFactories.get(requirement);
if (req != null)
tmf.attachRequirement(req);
else
tmf.addWaiting(requirement);
}
if (tmf.isConsistent())
factoryProcessor.add(tmf);
// processedFactories.add(tmf);
}
factoryProcessor.process();
/*log.debug("Added factory: " + tf.getId());
if (TrackedFactory.trackedFactoriesMap.containsKey(tf.getId())) {
TrackedFactory.trackedFactoriesMap.get(tf.getId()).unregister();
}
DefaultFactory defaultFactory = new DefaultFactory();
defaultFactory.setFactoryId(tf.getId());
defaultFactory.configure(tf.getConfigString());
if (defaultFactory.isValid()) {
tf.setId(defaultFactory.getFactoryId());
Dictionary<String, String> props = new Hashtable<String, String>();
props.put(EntaxyFactory.SERVICE.PROP_ID, defaultFactory.getFactoryId());
props.put(EntaxyFactory.SERVICE.PROP_TYPE, defaultFactory.getFactoryType());
props.put(EntaxyFactory.SERVICE.PROP_ORIGIN_BUNDLE, tf.getBundle().getBundleId()+"");
tf.setServiceRegistration(
this.bundleContext.registerService(EntaxyFactory.class, defaultFactory, props)
);
TrackedFactory.trackedFactoriesMap.put(tf.getId(), tf);
}*/
}
@Override
public void modified(List<TrackedFactory> managedObject) {
// TODO Auto-generated method stub
}
@Override
public void removed(List<TrackedFactory> managedObject) {
if (managedObject == null)
return;
for (TrackedFactory tf: managedObject) {
try {
tf.getServiceRegistration().unregister();
} catch (Exception e) {
// do nothing
}
TrackedFactory.trackedFactoriesMap.remove(tf.getId());
}
}
// implement FactoryConfigurationStorage
public JsonObject getConfiguration(String factoryId) {
TrackedManagedFactory tmf = managedFactories.get(factoryId);
if (tmf == null)
return null;
if (!tmf.isConsistent() || !tmf.isUpToDate)
return null;
return tmf.jsonEffective;
};
public List<TrackedManagedFactory> getManagedFactories() {
return new ArrayList<>(this.managedFactories.values());
}
protected class FactoryProcessor {
List<TrackedManagedFactory> factories = new ArrayList<>();
public void add(TrackedManagedFactory tmf) {
synchronized (factories) {
if (!factories.contains(tmf))
factories.add(tmf);
}
}
public void process() {
while (!this.factories.isEmpty()) {
List<TrackedManagedFactory> processed = new ArrayList<>();
List<TrackedManagedFactory> toProcess = new ArrayList<>();
for (TrackedManagedFactory currentFactory: factories) {
processFactory(currentFactory);
if (currentFactory.isActive) {
processed.add(currentFactory);
for (TrackedManagedFactory waitingFactory: managedFactories.values())
if (waitingFactory.isWaiting(currentFactory.factoryId)) {
waitingFactory.stopWaiting(currentFactory.factoryId);
if (waitingFactory.isConsistent())
toProcess.add(waitingFactory);
}
}
}
if (processed.isEmpty())
break;
for (TrackedManagedFactory tmf: toProcess)
if (!this.factories.contains(tmf))
this.factories.add(tmf);
for (TrackedManagedFactory tmf: processed)
this.factories.remove(tmf);
}
}
protected void processFactory(TrackedManagedFactory factory) {
if (factory.isConsistent()) {
if (CommonUtils.isValid(factory.parent)) {
TrackedManagedFactory parentF = managedFactories.get(factory.parent);
factory.attachParent(parentF);
}
for (String requirement: factory.requirements) {
TrackedManagedFactory req = managedFactories.get(requirement);
factory.attachRequirement(req);
}
JsonObject effective = EntaxyFactoryUtils.calculateEffectiveJson(factory.jsonOrigin, TrackedFactoryManager.this);
if (!effective.has("#CALC_ERROR")) {
factory.jsonEffective = effective.deepCopy();
JsonObject jsonFinal = EntaxyFactoryUtils.resolveVariants(effective);
// create factory
factory.updateConfiguration(jsonFinal);
if (createFactory(factory)) {
factory.activate();
}
} else {
JsonObject jo = effective.get("#CALC_ERROR").getAsJsonObject();
if (jo.has("#REQS")) {
JsonArray ja = jo.get("#REQS").getAsJsonArray();
final TrackedManagedFactory tmfF = factory;
ja.forEach(item->tmfF.addRequirement(item.getAsString()));
ja.forEach(item->tmfF.addWaiting(item.getAsString()));
}
}
} else {
log.info("Factory {} is inconsistent, waiting for: {}"
, factory.factoryId
, factory.waitingFor.stream().collect(Collectors.joining(",")));
}
}
public boolean createFactory(TrackedManagedFactory factory) {
DefaultFactory defaultFactory = new DefaultFactory();
defaultFactory.setFactoryId(factory.factoryId);
defaultFactory.configure(EntaxyFactoryUtils.cleanJson(factory.jsonFinal));
if (defaultFactory.isValid()) {
// to ensure id is correct
factory.trackedFactory.setId(defaultFactory.getId());
Dictionary<String, String> props = new Hashtable<String, String>();
props.put(EntaxyFactory.SERVICE.PROP_ID, defaultFactory.getId());
props.put(EntaxyFactory.SERVICE.PROP_TYPE, defaultFactory.getType());
props.put(EntaxyFactory.SERVICE.PROP_ORIGIN_BUNDLE, factory.trackedFactory.getBundle().getBundleId()+"");
factory.trackedFactory.setServiceRegistration(
TrackedFactoryManager.this.bundleContext.registerService(EntaxyFactory.class, defaultFactory, props)
);
return true;
}
return false;
}
}
public static class TrackedManagedFactory {
TrackedFactory trackedFactory;
// original JSON description
JsonObject jsonOrigin;
// JSON with inheritance & imports resolved
JsonObject jsonEffective;
// JSON with VARIANTS resolved
JsonObject jsonFinal;
JsonObject jsonFactorySection = null;
public String factoryId = null;
public String parent = null;
public List<String> requirements = new ArrayList<>();
TrackedManagedFactory parentFactory = null;
List<TrackedManagedFactory> requiredFactories = new ArrayList<>();
List<TrackedManagedFactory> affectedFactories = new ArrayList<>();
public List<String> waitingFor = new ArrayList<>();
public boolean isUpToDate = false;
public boolean isActive = false;
public TrackedManagedFactory(TrackedFactory factory) {
reload(factory);
}
public void reload(TrackedFactory factory) {
this.jsonFactorySection = null;
this.factoryId = null;
this.parent = null;
this.requirements = new ArrayList<>();
this.trackedFactory = factory;
this.waitingFor.clear();
this.jsonOrigin = JSONUtils.getJsonRootObject(this.trackedFactory.getConfigString());
if (this.jsonOrigin.has(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME)) {
this.jsonFactorySection = this.jsonOrigin.get(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME).getAsJsonObject();
if (this.jsonFactorySection.has(EntaxyFactory.CONFIGURATION.FACTORY.ID))
this.factoryId = this.jsonFactorySection.get(EntaxyFactory.CONFIGURATION.FACTORY.ID).getAsString();
if (this.jsonFactorySection.has(EntaxyFactory.CONFIGURATION.FACTORY.PARENT))
this.parent = this.jsonFactorySection.get(EntaxyFactory.CONFIGURATION.FACTORY.PARENT).getAsString();
if (this.jsonFactorySection.has(EntaxyFactory.CONFIGURATION.FACTORY.REQUIRES)) {
JsonElement je = this.jsonFactorySection.get(EntaxyFactory.CONFIGURATION.FACTORY.REQUIRES);
if (je.isJsonArray()) {
JsonArray ja = je.getAsJsonArray();
ja.forEach(item->this.requirements.add(item.getAsString()));
}
if (je.isJsonPrimitive()) {
this.requirements.add(je.getAsString());
}
}
}
}
public boolean isValid() {
return CommonUtils.isValid(factoryId);
}
public void setParent(String parentValue) {
this.parent = parentValue;
JsonObject fs = this.jsonOrigin.get(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME).getAsJsonObject();
fs.remove(EntaxyFactory.CONFIGURATION.FACTORY.PARENT);
fs.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.PARENT, parentValue);
}
public void attachParent(TrackedManagedFactory parentTmf) {
if (this.parentFactory != parentTmf)
detachParent();
this.parentFactory = parentTmf;
parentTmf.attachAffected(this);
this.isUpToDate = false;
}
public void detachParent() {
if (this.parentFactory != null) {
this.parentFactory.detachAffected(this);
this.parentFactory = null;
}
}
public void addRequirement(String req) {
if (!this.requirements.contains(req))
this.requirements.add(req);
}
public void attachRequirement(TrackedManagedFactory requiredTmf) {
if (!this.requiredFactories.contains(requiredTmf))
this.requiredFactories.add(requiredTmf);
requiredTmf.attachAffected(this);
}
public void detachRequirements() {
for (TrackedManagedFactory tmf: requiredFactories)
tmf.detachAffected(this);
this.requiredFactories.clear();
}
public void attachAffected(TrackedManagedFactory affectedTmf) {
if (!this.affectedFactories.contains(affectedTmf))
this.affectedFactories.add(affectedTmf);
}
public void detachAffected(TrackedManagedFactory affectedTmf) {
this.affectedFactories.remove(affectedTmf);
}
public void addWaiting(String waitFactoryId) {
if (!this.waitingFor.contains(waitFactoryId))
this.waitingFor.add(waitFactoryId);
}
public boolean isWaiting(String waitFactoryId) {
return this.waitingFor.contains(waitFactoryId);
}
public void stopWaiting(String waitFactoryId) {
this.waitingFor.remove(waitFactoryId);
}
public boolean isConsistent() {
return this.waitingFor.isEmpty();
}
public void updateConfiguration(JsonObject jsonObject) {
this.jsonFinal = jsonObject.deepCopy();
this.isUpToDate = true;
}
public void activate() {
this.isActive = true;
}
public void deactivate() {
this.trackedFactory.unregister();
this.isActive = false;
}
}
}

View File

@ -0,0 +1,62 @@
{
"factory": {
"id": "base-object",
"type": "entaxy.runtime.object",
"description": "Abstract object",
"isAbstract": true,
"label": "object"
},
"entaxy.runtime.object": {
"isEntaxyObject": true
},
"fields": {
"objectId": {
"displayName": "Object Id",
"type": "String",
"required": true,
"immutable": true,
"addToOutput": "*"
},
"##publish": {
"type": "Map",
"required": true,
"isHidden": true,
"configurable": false,
"defaultValue":{
"name": {
"@CALCULATED": {
"expression": "${objectId}",
"lazy": true
}
},
"factory": {
"@CALCULATED": {
"expression": "${factoryId}",
"lazy": false
}
},
"label": {
"@CALCULATED": {
"expression": "${#FACTORY#.factory.label}",
"lazy": false
}
},
"scope": {
"@CALCULATED": {
"expression": "${scope}",
"allowObjects": false,
"lazy": false
}
}
}
}
},
"outputs": {
"init": {
"isDefault": true,
"fields": {
"##publish": {}
}
}
}
}