All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nedap.archie.flattener.Flattener Maven / Gradle / Ivy

Go to download

tools that operate on the archie reference models and archetype object model

There is a newer version: 3.12.0
Show newest version
package com.nedap.archie.flattener;

import com.nedap.archie.adlparser.modelconstraints.ReflectionConstraintImposer;
import com.nedap.archie.aom.*;
import com.nedap.archie.aom.utils.ArchetypeParsePostProcesser;
import com.nedap.archie.rminfo.MetaModels;
import com.nedap.archie.rminfo.ReferenceModels;
import org.openehr.bmm.v2.validation.BmmRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import static com.nedap.archie.flattener.FlattenerUtil.getPossiblyOverridenListValue;
import static com.nedap.archie.flattener.FlattenerUtil.getPossiblyOverridenValue;

/**
 * Flattener. For single use only, create a new flattener for every flatten-action you want to do!
 *
 * Created by pieter.bos on 21/10/15.
 */
public class Flattener implements IAttributeFlattenerSupport {

    private final MetaModels metaModels;
    //to be able to store Template Overlays transparently during flattening
    private OverridingArchetypeRepository repository;

    private Archetype parent;
    private Archetype child;

    private Archetype result;
    private final FlattenerConfiguration config;

    private RulesFlattener rulesFlattener = new RulesFlattener();
    private AnnotationsFlattener annotationsFlattener = new AnnotationsFlattener();

    CAttributeFlattener cAttributeFlattener = new CAttributeFlattener(this);
    private TupleFlattener tupleFlattener = new TupleFlattener();

    private OperationalTemplateCreator optCreator = new OperationalTemplateCreator(this);



    public Flattener(ArchetypeRepository repository, ReferenceModels models) {
        this.repository = new OverridingArchetypeRepository(repository);
        this.metaModels = new MetaModels(models, (BmmRepository) null);
        config = FlattenerConfiguration.forFlattened();
    }

    public Flattener(ArchetypeRepository repository, MetaModels models) {
        this.repository = new OverridingArchetypeRepository(repository);
        this.metaModels = models;
        config = FlattenerConfiguration.forFlattened();
    }

    public Flattener(ArchetypeRepository repository, MetaModels models, FlattenerConfiguration configuration) {
        this.repository = new OverridingArchetypeRepository(repository);
        this.metaModels = models;
        this.config = configuration.clone();
    }

    /**
     * Create operational templates in addition to flattening. Default is false;
     * @param makeTemplate
     * @return
     */
    public Flattener createOperationalTemplate(boolean makeTemplate) {
        config.setCreateOperationalTemplate(makeTemplate);
        if(makeTemplate) {
            config.setRemoveZeroOccurrencesObjects(true);
        }
        return this;
    }

    /**
     * Remove zero occurrences constraints, instead of leaving them but removing all of their children
     *
     * Default is false
     * @param remove
     * @return
     */
    public Flattener removeZeroOccurrencesConstraints(boolean remove) {
        config.setRemoveZeroOccurrencesObjects(remove);
        return this;
    }

    /**
     * if this flattener is setup to create operational templates, also set it to remove all languages from the terminology
     * except for the given languages
     * @param languages
     * @return
     */
    public Flattener keepLanguages(String... languages) {
        config.setLanguagesToKeep(languages);
        return this;
    }

    public Flattener removeLanguagesFromMetadata(boolean remove) {
        config.setRemoveLanguagesFromMetaData(remove);
        return this;
    }

    public Archetype flatten(Archetype toFlatten) {
        if(parent != null) {
            throw new IllegalStateException("You've used this flattener before - single use instance, please create a new one!");
        }

        metaModels.selectModel(toFlatten);

        //validate that we can legally flatten first
        String parentId = toFlatten.getParentArchetypeId();
        if(parentId == null) {
            if(config.isCreateOperationalTemplate()) {
                OperationalTemplate template = optCreator.createOperationalTemplate(toFlatten);
                result = template;
                //make an operational template by just filling complex object proxies and archetype slots
                optCreator.fillSlots(template);
                fillOptEmptyOccurrences(result);
                TerminologyFlattener.filterLanguages(template, config.isRemoveLanguagesFromMetaData(), config.getLanguagesToKeep());
                result = template;
            } else {
                result = toFlatten.clone();
            }
            result.getDefinition().setArchetype(result);
            result.setDifferential(false);
            result.setGenerated(true);
            return result;
        }

        this.parent = repository.getArchetype(toFlatten.getParentArchetypeId());
        if(parent == null) {
            throw new IllegalArgumentException("parent archetype not found in repository: " + toFlatten.getParentArchetypeId());
        }
        this.child = toFlatten.clone();//just to be sure, so we don't have to copy more things deeper down


        if(child instanceof Template) {
            Template childTemplate = (Template) child;
            for(TemplateOverlay overlay:childTemplate.getTemplateOverlays()) {
                //we'll flatten them later when we need them, otherwise, you run into problems with archetypes
                //not yet added to repository while we already need them
                repository.addExtraArchetype(overlay);
            }
        }

        if(parent.getParentArchetypeId() != null) {
            //parent needs flattening first
            parent = getNewFlattenerForParent().flatten(parent);
        }


        this.result = null;
        if(config.isCreateOperationalTemplate()) {
            result = optCreator.createOperationalTemplate(parent);
            optCreator.overrideArchetypeId(result, child);
        } else {
            result = child.clone();

            Archetype clonedParent = parent.clone();
            //definition, terminology and rules will be replaced later, but must be set to that of the parent
            // for this flattener to work correctly. I would not write it this way when creating another flattener, but
            //it's the way it is :)
            //parent needs to be cloned because this updates references to parent archetype as well
            result.setDefinition(clonedParent.getDefinition());
            result.setTerminology(clonedParent.getTerminology());
            result.setRules(clonedParent.getRules());
        }
        new AnnotationsFlattener().flatten(parent, child, result);

        //1. redefine structure
        //2. fill archetype slots if we are creating an operational template
        flattenDefinition(result, child);

        if(config.isCreateOperationalTemplate() && config.isRemoveZeroOccurrencesObjects()) {
            optCreator.removeZeroOccurrencesConstraints(result);
        } else {
            prohibitZeroOccurrencesConstraints(result);
        }

        String prefix = child.getArchetypeId().getConceptId() + "_";
        //Use empty tagPrefix here. If not empty, overridden rules in specialized archetype will not overwrite base rules,
        //but be added to the rules section additionally to the base rules.
        rulesFlattener.combineRules(child, result, prefix, "", "", true /* override statements with same tag */);
        if(config.isCreateOperationalTemplate()) {
            optCreator.fillSlots((OperationalTemplate) result);

        }
        fillOptEmptyOccurrences(result);
        TerminologyFlattener.flattenTerminology(result, child);

        if(config.isCreateOperationalTemplate()) {
            TerminologyFlattener.filterLanguages((OperationalTemplate) result, config.isRemoveLanguagesFromMetaData(), config.getLanguagesToKeep());
        }
        result.getDefinition().setArchetype(result);
        result.setDescription(child.getDescription());
        result.setOtherMetaData(child.getOtherMetaData());
        result.setBuildUid(child.getBuildUid());
        result.setOriginalLanguage(child.getOriginalLanguage());
        result.setTranslations(child.getTranslations());

        if(child instanceof Template && !config.isCreateOperationalTemplate()) {
            Template resultTemplate = (Template) result;
            resultTemplate.setTemplateOverlays(new ArrayList<>());
            Template childTemplate = (Template) child;
            //we need to add the flattened template overlays. For operational template these have been added to the archetype structure, so not needed
            for(TemplateOverlay overlay:((Template) child).getTemplateOverlays()){
                TemplateOverlay flatOverlay = (TemplateOverlay) getNewFlattener().flatten(overlay);
                ResourceDescription description = (ResourceDescription) result.getDescription().clone();
                //not sure whether to do this or to implement these methods using the owningTemplate param.
                //in many cases you do want this information...
                flatOverlay.setDescription(description);
                flatOverlay.setOriginalLanguage(result.getOriginalLanguage());
                flatOverlay.setTranslationList(result.getTranslationList());
                ArchetypeParsePostProcesser.fixArchetype(flatOverlay);
                resultTemplate.getTemplateOverlays().add(flatOverlay);
            }
        }

        this.removeSiblingOrder(result);

        result.setDifferential(false);//mark this archetype as being flat
        result.setGenerated(true);

        ArchetypeParsePostProcesser.fixArchetype(result);

        //set the single/multiple attributes correctly
        new ReflectionConstraintImposer(metaModels.getSelectedModel())
                .setSingleOrMultiple(result.getDefinition());

        return result;
    }

    private void fillOptEmptyOccurrences(Archetype result) {
        if(config.isCreateOperationalTemplate() && config.isFillEmptyOccurrences()) {
            optCreator.fillEmptyOccurrences(result);
        }
    }

    /** Zero occurrences and existence constraint processing when flattening. Does not remove attributes*/
    private void prohibitZeroOccurrencesConstraints(Archetype archetype) {
        Stack workList = new Stack<>();
        workList.push(archetype.getDefinition());
        while(!workList.isEmpty()) {
            CObject object = workList.pop();
            for(CAttribute attribute:object.getAttributes()) {
                if(attribute.getExistence() != null && attribute.getExistence().getUpper() == 0 && !attribute.getExistence().isUpperUnbounded()) {
                    //remove children, but do not remove attribute itself to make sure it stays prohibited
                    attribute.setChildren(new ArrayList<>());
                } else {
                    List objectsToRemove = new ArrayList<>();
                    for (CObject child : attribute.getChildren()) {
                        if (!child.isAllowed()) {
                            if(child instanceof CComplexObject) {
                                ((CComplexObject) child).setAttributes(new ArrayList<>());
                            }
                            if(config.isRemoveZeroOccurrencesObjects()) {
                                objectsToRemove.add(child);
                            }
                        } else {
                            workList.push(child);
                        }
                    }
                    attribute.getChildren().removeAll(objectsToRemove);
                }

            }

        }
    }

    private void removeSiblingOrder(Archetype archetype) {
        Stack workList = new Stack<>();
        workList.push(archetype.getDefinition());
        while(!workList.isEmpty()) {
            CObject object = workList.pop();
            for (CAttribute attribute : object.getAttributes()) {
                for (CObject child : attribute.getChildren()) {
                    workList.push(child);
                    if (child.getSiblingOrder() != null) {
                        child.setSiblingOrder(null);
                    }
                }
            }
        }
    }

    private void flattenDefinition(Archetype parent, Archetype specialized) {
        parent.setArchetypeId(specialized.getArchetypeId()); //TODO: override all metadata?
        createSpecializeCObject(null, parent.getDefinition(), specialized.getDefinition());

    }


    @Override
    public CObject createSpecializeCObject(CAttribute attribute, CObject parent, CObject specialized) {
        if(parent == null) {
            return specialized;//TODO: clone?
        }
        CObject newObject = cloneSpecializedObject(attribute, parent, specialized);

        specializeOccurrences(specialized, newObject);
        newObject.setSiblingOrder(getPossiblyOverridenValue(newObject.getSiblingOrder(), specialized.getSiblingOrder()));

        newObject.setNodeId(getPossiblyOverridenValue(newObject.getNodeId(), specialized.getNodeId()));
        newObject.setRmTypeName(getPossiblyOverridenValue(newObject.getRmTypeName(), specialized.getRmTypeName()));

        //now specialize the structure under the specialized node
        specializeContent(parent, specialized, newObject);
        return newObject;
    }

    private void specializeContent(CObject parent, CObject specialized, CObject newObject) {

        if (parent instanceof CComplexObject) {
            if(((CComplexObject) parent).isAnyAllowed() && specialized instanceof CComplexObjectProxy) {
                //you can replace an any allowed node with a CComplexObjectProxy. No content will need to be specialized, just merge it in
            }
            else if(!(specialized instanceof CComplexObject)) {
                //this is the specs. The ADL workbench allows an ARCHETYPE_SLOT to override a C_ARCHETYPE_ROOT without errors. Filed as https://openehr.atlassian.net/projects/AWBPR/issues/AWBPR-72
                throw new IllegalArgumentException(String.format("cannot override complex object %s (%s) with non-complex object %s (%s)", parent.path(), parent.getClass().getSimpleName(), specialized.path(), specialized.getClass().getSimpleName()));
            } else {
                flattenCComplexObject((CComplexObject) newObject, (CComplexObject) specialized);
            }
        }
        else if (newObject instanceof ArchetypeSlot) {//archetypeslot is NOT a complex object. It's replacement can be
            if(specialized instanceof ArchetypeSlot) {
                flattenArchetypeSlot((ArchetypeSlot) newObject, (ArchetypeSlot) specialized);
            } else if(specialized instanceof CArchetypeRoot) {
                //TODO: handle as if this is a template overlay, but inline. Probably needed in the fillArchetypeRoot method, not here?
            } else {
                throw new IllegalArgumentException("Can only replace an archetype slot with an archetype root or another archetype slot, not with a " + newObject.getClass());
            }
        }
    }

    private void specializeOccurrences(CObject specialized, CObject newObject) {
        //TODO: check if overriding occurrences is allowed
        newObject.setOccurrences(getPossiblyOverridenValue(newObject.getOccurrences(), specialized.getOccurrences()));
    }

    private CObject cloneSpecializedObject(CAttribute attribute, CObject parent, CObject specialized) {
        CObject newObject;
        if(attribute == null) {
            //root of archetype. don't clone anything.. alternative: make a mock attribute at the root
            newObject = parent;
        } else {
            newObject = (CObject) parent.clone();
        }
        if(newObject instanceof ArchetypeSlot && specialized instanceof CArchetypeRoot) {
            newObject = (CObject) specialized.clone();
        }
        return newObject;
    }

    private void flattenArchetypeSlot(ArchetypeSlot parent, ArchetypeSlot specialized) {
        if(specialized.isClosed()) {
            parent.setClosed(true);
        }
        parent.setIncludes(getPossiblyOverridenListValue(parent.getIncludes(), specialized.getIncludes()));
        parent.setExcludes(getPossiblyOverridenListValue(parent.getExcludes(), specialized.getExcludes()));

        //TODO: includes/excludes?
    }

    /**
     * Flatten a CComplexObject. newObject must be a clone of the original parent, specialized the original unmodified
     * specialized node.
     *
     * The attributes of newObject will be changed in place, so newObject will be altered in this operation
     *
     * @param newObject
     * @param specialized
     */
    private void flattenCComplexObject(CComplexObject newObject, CComplexObject specialized) {

        if(specialized instanceof CArchetypeRoot && newObject instanceof CArchetypeRoot) {
            //cloneSpecializedObject() will already have handled the case where the parent is an ARCHETYPE_SLOT
            //and the child is a C_ARCHETYPE_ROOT by cloning the child instead of the parent
            //handle redefinition of CArchetypeRoots here.
            CArchetypeRoot specializedArchetypeRoot = (CArchetypeRoot) specialized;
            if(specializedArchetypeRoot.getArchetypeRef() != null) {
                CArchetypeRoot newArchetypeRoot = (CArchetypeRoot) newObject;
                newArchetypeRoot.setArchetypeRef(specializedArchetypeRoot.getArchetypeRef());
            }
        }

        for(CAttribute attribute:specialized.getAttributes()) {
            cAttributeFlattener.flattenSingleAttribute(newObject, attribute);
        }
        for(CAttributeTuple tuple:specialized.getAttributeTuples()) {
            tupleFlattener.flattenTuple(newObject, tuple);
        }
    }

    /**
     * Get a new flattener to flatten parent archetypes. Works the same as {@link #getNewFlattener()}, except that
     * it will remove zero occurrences constraints in parents if so configured.
     *
     * @return
     */
    protected Flattener getNewFlattenerForParent() {
        Flattener result = new Flattener(repository, metaModels, config)
                .createOperationalTemplate(false); //do not create operational template except at the end.
        if(config.isRemoveZeroOccurrencesInParents()) {
            //remove all zero occurrences objects EXCEPT in the top level archetype
            //so that you can see that things have been removed that you can still edit - but not others
            result.removeZeroOccurrencesConstraints(true);
        }
        return result;
    }

    /**
     * Get a new flattener with the same configuration as this, except that it will not create operational templates
     * 
* The not creating operational templates is because the operational template creator needs to be done only for the * final result, not the intermediate steps * @return */ protected Flattener getNewFlattener() { return new Flattener(repository, metaModels, config) .createOperationalTemplate(false); //do not create operational template except at the end. } private Flattener useComplexObjectForArchetypeSlotReplacement(boolean useComplexObjectForArchetypeSlotReplacement) { config.setUseComplexObjectForArchetypeSlotReplacement(useComplexObjectForArchetypeSlotReplacement); return this; } public boolean isUseComplexObjectForArchetypeSlotReplacement() { return config.isUseComplexObjectForArchetypeSlotReplacement(); } @Override public MetaModels getMetaModels() { return metaModels; } public boolean getCreateOperationalTemplate() { return config.isCreateOperationalTemplate(); } protected RulesFlattener getRulesFlattener() { return rulesFlattener; } protected AnnotationsFlattener getAnnotationsFlattener() { return annotationsFlattener; } public OverridingArchetypeRepository getRepository() { return repository; } FlattenerConfiguration getConfiguration() { return config; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy