/** | |
******************************************************************************** | |
* Copyright (c) 2015-2021 Robert Bosch GmbH 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/ | |
* | |
* SPDX-License-Identifier: EPL-2.0 | |
* | |
* Contributors: | |
* Robert Bosch GmbH - initial API and implementation | |
******************************************************************************** | |
*/ | |
package org.eclipse.app4mc.amalthea.converters081.impl; | |
import java.io.File; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.StringTokenizer; | |
import org.eclipse.app4mc.amalthea.converters.common.ServiceConstants; | |
import org.eclipse.app4mc.amalthea.converters.common.base.ICache; | |
import org.eclipse.app4mc.amalthea.converters.common.base.IConverter; | |
import org.eclipse.app4mc.amalthea.converters.common.converter.AbstractConverter; | |
import org.eclipse.app4mc.amalthea.converters.common.utils.AmaltheaNamespaceRegistry; | |
import org.eclipse.app4mc.amalthea.converters.common.utils.HelperUtil; | |
import org.eclipse.app4mc.amalthea.converters081.utils.ConstraintElementsCacheBuilder; | |
import org.eclipse.app4mc.amalthea.converters081.utils.ConstraintElementsCacheEnum; | |
import org.eclipse.app4mc.util.sessionlog.SessionLogger; | |
import org.jdom2.Attribute; | |
import org.jdom2.Document; | |
import org.jdom2.Element; | |
import org.osgi.service.component.annotations.Activate; | |
import org.osgi.service.component.annotations.Component; | |
import org.osgi.service.component.annotations.Reference; | |
/** | |
* This class is responsible for converting the Constraints Model elements from 0.8.0 to 0.8.1 version format of | |
* AMALTHEA model | |
* | |
* @author mez2rng | |
* | |
*/ | |
@Component( | |
property = { | |
ServiceConstants.INPUT_MODEL_VERSION_PROPERTY + "=0.8.0", | |
ServiceConstants.OUTPUT_MODEL_VERSION_PROPERTY + "=0.8.1"}, | |
service = IConverter.class) | |
public class ConstraintsConverter extends AbstractConverter { | |
private static final String AMLT_PREFIX = "amlt:/#"; | |
private static final String HREF = "href"; | |
private static final String TYPE = "type"; | |
private static final String XSI = "xsi"; | |
private static final String VALUE = "value"; | |
private static final String SCOPE = "scope"; | |
private static final String EVENT_CHAIN = "eventChain"; | |
@Reference | |
SessionLogger logger; | |
private ConstraintElementsCacheBuilder cache; | |
@Override | |
@Activate | |
protected void activate(Map<String, Object> properties) { | |
super.activate(properties); | |
} | |
@Override | |
public void convert(File targetFile, Map<File, Document> fileDocumentMapping, List<ICache> caches) { | |
logger.info("Migration from 0.8.0 to 0.8.1 : Executing Constraints converter for model file : {0}", | |
targetFile.getName()); | |
this.cache = getConstraintElementsCacheBuilder(caches); | |
if (this.cache == null) { | |
throw new IllegalStateException("ConstraintElementsCacheBuilder is not built and Object of it is not available in Converters"); | |
} | |
final Document root = fileDocumentMapping.get(targetFile); | |
if (root == null) { | |
return; | |
} | |
final Element rootElement = root.getRootElement(); | |
updateEventChainElementDefinitions_and_references(rootElement); | |
} | |
/** | |
* This method is used for the migration of EventChain definitions and references present inside the Constraints | |
* model (For further details, check : Bug 518119 ) | |
* | |
* @param rootElement | |
* Amalthea root element | |
*/ | |
private void updateEventChainElementDefinitions_and_references(final Element rootElement) { | |
final List<Element> rootEventChainElements = HelperUtil.getXpathResult( | |
rootElement, | |
"./constraintsModel/eventChains", | |
Element.class, | |
AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
final List<String> allRootEventChainElements = getAllRootEventChainElements(); | |
if (! rootEventChainElements.isEmpty()) { | |
for (final Element rootEventChainElement : rootEventChainElements) { | |
final List<Element> subEventChainElements = HelperUtil.getXpathResult( | |
rootEventChainElement, | |
".//*[@xsi:type=\"am:SubEventChain\"]", | |
Element.class, | |
AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
for (final Element subEventChainElement : subEventChainElements) { | |
final Attribute typeAttribute = subEventChainElement.getAttribute(TYPE, | |
AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
if (typeAttribute != null) { | |
typeAttribute.setValue("am:EventChainContainer"); | |
} | |
} | |
updateEventChainReferences(allRootEventChainElements, rootEventChainElement); | |
} | |
} | |
updateEventChainReferences_in_TimingConstraints(allRootEventChainElements, rootElement); | |
} | |
/** | |
* This method is used to migrate the contents of TimingConstraint elements of type EventChainLatencyConstraint and | |
* EventChainSynchronizationConstraint, which are present inside Constraints Model. | |
* | |
* <b>Note:</b> As a part of migration, it should be ensured that sub EventChain elements are not directly referred | |
* inside TimingConstraint elements. Till 0.8.1, it was possible to refer sub EventChain elements --> but based on | |
* this change such references are removed. To preserve the data specified by the user, reference String is stored | |
* as a CustomProperty | |
* | |
* | |
* @param allRootEventChainElements | |
* List<String> contains the names of all the root EventChain elements (in the entire model scope) | |
* @param rootElement | |
* Amalthea root element | |
* | |
*/ | |
private void updateEventChainReferences_in_TimingConstraints(final List<String> allRootEventChainElements, final Element rootElement) { | |
final List<Element> eventChainReferenceEleemnts = HelperUtil.getXpathResult( | |
rootElement, | |
"./constraintsModel/timingConstraints[@xsi:type=\"am:EventChainLatencyConstraint\" or @xsi:type=\"am:EventChainSynchronizationConstraint\"]", | |
Element.class, | |
AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
for (final Element timingConstraint : eventChainReferenceEleemnts) { | |
final List<String> invalidEventChainRefs = new ArrayList<>(); | |
final Attribute eventChainsAttribute = timingConstraint.getAttribute(SCOPE); | |
if (eventChainsAttribute != null) { | |
boolean isRefEventChainStringsUpdated = false; | |
String refEventChainStrings = eventChainsAttribute.getValue(); | |
final StringTokenizer stringTokenizer = new StringTokenizer(refEventChainStrings); | |
while (stringTokenizer.hasMoreTokens()) { | |
final String refEventChainString = stringTokenizer.nextToken(); | |
final String refEventChainName = refEventChainString.substring(0, refEventChainString.lastIndexOf('?')); | |
/*- performing validation, to identify if childelements are referred */ | |
if (!allRootEventChainElements.contains(refEventChainName)) { | |
isRefEventChainStringsUpdated = true; | |
/*- this is the case child EventChain element is referred (Note: As per AMALTHEA 0.8.1, it is not supported)*/ | |
refEventChainStrings = refEventChainStrings.replace(refEventChainString, ""); | |
/*- adding invalid EventChain reference string to a list -> to create CustomProperty object later */ | |
invalidEventChainRefs.add(refEventChainString); | |
if (refEventChainStrings.trim().length() == 0) { | |
timingConstraint.removeAttribute(eventChainsAttribute); | |
} | |
logEventChainMessage_TimingConstraint(timingConstraint, refEventChainName); | |
} | |
} | |
if (isRefEventChainStringsUpdated) { | |
/*- setting the updated EventChain reference String */ | |
eventChainsAttribute.setValue(refEventChainStrings.trim()); | |
} | |
} | |
/*- | |
* If EventChain element is defined in other model file, then the reference inside TimingConstraint element will be generated as a separate tag in XMI | |
* | |
* Example: | |
* | |
* <timingConstraints xsi:type="am:EventChainLatencyConstraint" name="ecl2"> | |
* <scope href="amlt:/#eventChain_from_second_file_sub_element?type=EventChain"/> | |
* </timingConstraints> | |
* */ | |
final List<Element> eventChains = timingConstraint.getChildren(SCOPE); | |
if (eventChains != null) { | |
for (final Element eventChainElement : eventChains) { | |
final String hrefValue = eventChainElement.getAttributeValue(HREF); | |
if (hrefValue != null && hrefValue.contains(AMLT_PREFIX)) { | |
final String refEventChainName = hrefValue.substring(hrefValue.indexOf('#') + 1, hrefValue.indexOf('?')); | |
/*- performing validation, to identify if childelements are referred */ | |
if (!allRootEventChainElements.contains(refEventChainName)) { | |
/*- this is the case child EventChain element is referred (Note: As per AMALTHEA 0.8.1, it is not supported)*/ | |
timingConstraint.removeContent(eventChainElement); | |
invalidEventChainRefs.add(hrefValue); | |
logEventChainMessage_TimingConstraint(timingConstraint, refEventChainName); | |
} | |
} | |
} | |
} | |
/*- | |
* =================== Creating CustomProperty and associating invalid EventChain reference Strings to it. This step is performed to avoid loss of data =========== | |
*/ | |
if (! invalidEventChainRefs.isEmpty()) { | |
final Element customPropertyElement = new Element("customProperties"); | |
customPropertyElement.setAttribute("key", SCOPE); | |
final Element valueElement = new Element(VALUE); | |
valueElement.setAttribute(TYPE, "am:ListObject", AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
for (final String invalidEventChainRef : invalidEventChainRefs) { | |
final Element valuesElement = new Element("values"); | |
valuesElement.setAttribute(TYPE, "am:StringObject", AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
valuesElement.setAttribute(VALUE, invalidEventChainRef); | |
valueElement.addContent(valuesElement); | |
} | |
customPropertyElement.addContent(valueElement); | |
timingConstraint.addContent(customPropertyElement); | |
} | |
} | |
} | |
/** | |
* This method is used to migrate the contents of EventChainReference objects which are present inside EventChain | |
* object in Constraints Model. | |
* | |
* <b>Note:</b> As a part of migration, it should be ensured that sub EventChain elements are not directly referred | |
* inside EventChainReference elements. Till 0.8.1, there was a possibility to refer sub EventChain elements --> but | |
* based on this change such references are removed. To preserve the data specified by the user, reference String is | |
* stored as a CustomProperty | |
* | |
* | |
* @param allRootEventChainElements | |
* List<String> contains the names of all the root EventChain elements (in the entire model scope) | |
* @param rootEventChainElement | |
* EventChain element which is directly present inside Constraints Model | |
*/ | |
private void updateEventChainReferences(final List<String> allRootEventChainElements, final Element rootEventChainElement) { | |
final List<Element> eventChainReferenceEleemnts = HelperUtil.getXpathResult( | |
rootEventChainElement, | |
".//*[@xsi:type=\"am:EventChainReference\"]", | |
Element.class, | |
AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
for (final Element eventChainReferenceElement : eventChainReferenceEleemnts) { | |
final Attribute eventChainReferenceAttribute = eventChainReferenceElement.getAttribute(EVENT_CHAIN); | |
if (eventChainReferenceAttribute != null) { | |
final String refEventChainString = eventChainReferenceAttribute.getValue(); | |
if (refEventChainString != null && refEventChainString.contains("?")) { | |
final String refEventChainName = refEventChainString.substring(0, | |
refEventChainString.lastIndexOf('?')); | |
/*- performing validation, to identify if childelements are referred */ | |
/* | |
* ASSUMPTION !!! : In case of model element reference, elements name should be already encoded as a part of the reference. | |
* | |
* allRootEventChainElements -> obtained from the CacheBuilder should already contain encoded names of the EventChain elements | |
*/ | |
if (!allRootEventChainElements.contains((refEventChainName))) { | |
/*- this is the case child EventChain element is referred (Note: As per AMALTHEA 0.8.1, it is not supported)*/ | |
eventChainReferenceElement.removeAttribute(eventChainReferenceAttribute); | |
logEventChainMessage_and_Create_CustomProperty(rootEventChainElement, | |
eventChainReferenceElement, refEventChainString, refEventChainName); | |
} | |
} | |
} | |
/*- | |
* if EventChain element is defined in other model file, then the eventChain element will be generated as a separate tag in XMI | |
* | |
* Example: | |
* | |
* <segments xsi:type="am:EventChainReference"> | |
* <eventChain href="amlt:/#eventChain_from_second_file?type=EventChain"/> | |
* </segments> | |
* | |
* */ | |
final Element eventChainElement = eventChainReferenceElement.getChild(EVENT_CHAIN); | |
if (eventChainElement != null) { | |
final String hrefValue = eventChainElement.getAttributeValue(HREF); | |
if (hrefValue != null && hrefValue.contains(AMLT_PREFIX)) { | |
final String refEventChainName = hrefValue.substring(hrefValue.indexOf('#') + 1, | |
hrefValue.indexOf('?')); | |
/*- performing validation, to identify if childelements are referred */ | |
/* | |
* ASSUMPTION !!! : In case of model element reference (refEventChainName), elements name should be already encoded as a part of the reference. | |
* | |
* allRootEventChainElements -> obtained from the CacheBuilder should already contain encoded names of the EventChain elements | |
*/ | |
if (!allRootEventChainElements.contains((refEventChainName))) { | |
/*- this is the case child EventChain element is referred (Note: As per AMALTHEA 0.8.1, it is not supported)*/ | |
eventChainReferenceElement.removeContent(eventChainElement); | |
logEventChainMessage_and_Create_CustomProperty(rootEventChainElement, | |
eventChainReferenceElement, hrefValue, refEventChainName); | |
} | |
} | |
} | |
} | |
} | |
private void logEventChainMessage_TimingConstraint(final Element timingConstraintElement, | |
final String refEventChainName) { | |
logger.warn("Sub EventChain : {0} is referred inside TimingConstraint as \"Scope\": {1}. " | |
+ "As per 0.8.1, it is not valid to refer sub EventChain elements directly.\n\r" | |
+ "EventChain {2}'s association is removed from TimingConstraint object, and corresponding information is stored as a CustomProperty", | |
HelperUtil.decodeName(refEventChainName), | |
timingConstraintElement.getAttributeValue("name"), | |
refEventChainName); | |
} | |
private void logEventChainMessage_and_Create_CustomProperty(final Element rootEventChainElement, | |
final Element eventChainReferenceElement, final String refEventChainString, | |
final String refEventChainName) { | |
logger.warn("Sub EventChain : {0} is referred inside EventChainReference of EventChain : {1}." | |
+ " As per 0.8.1, it is not valid to refer sub EventChain elements inside EventChainReference.\n\r" | |
+ " eventChain association is removed from EventChainReference object, and corresponding information is stored as a CustomProperty", | |
HelperUtil.decodeName(refEventChainName), | |
rootEventChainElement.getAttributeValue("name")); | |
final Element customPropertyElement = new Element("customProperties"); | |
customPropertyElement.setAttribute("key", EVENT_CHAIN); | |
final Element valueElement = new Element(VALUE); | |
valueElement.setAttribute(TYPE, "am:StringObject", AmaltheaNamespaceRegistry.getGenericNamespace(XSI)); | |
valueElement.setAttribute(VALUE, refEventChainString); | |
customPropertyElement.addContent(valueElement); | |
eventChainReferenceElement.addContent(customPropertyElement); | |
} | |
/** | |
* This method returns the names of all root EventChain elements -- from different AMALTHEA models which are in | |
* folder scope | |
* | |
* @return | |
*/ | |
@SuppressWarnings("unchecked") | |
private List<String> getAllRootEventChainElements() { | |
ArrayList<String> rootEventChainNames = new ArrayList<>(); | |
Map<File, Map<String, Object>> cacheMap = this.cache.getCacheMap(); | |
Collection<Map<String, Object>> values = cacheMap.values(); | |
for (Map<String, Object> map : values) { | |
Object value = map.get(ConstraintElementsCacheEnum.ROOT_EVENTCHAIN_NAMES.name()); | |
if (value instanceof List<?>) { | |
rootEventChainNames.addAll((Collection<? extends String>) value); | |
} | |
} | |
return rootEventChainNames; | |
} | |
/** | |
* This method is used to get the ConstraintElementsCacheBuilder object | |
* | |
* @param caches The list of all caches. | |
* @return ConstraintElementsCacheBuilder | |
*/ | |
private ConstraintElementsCacheBuilder getConstraintElementsCacheBuilder(List<ICache> caches) { | |
if (caches != null) { | |
for (ICache c : caches) { | |
if (c instanceof ConstraintElementsCacheBuilder) { | |
return (ConstraintElementsCacheBuilder) c; | |
} | |
} | |
} | |
return null; | |
} | |
} |