001    package de.deepamehta.plugins.typeeditor;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.Topic;
005    import de.deepamehta.core.Type;
006    import de.deepamehta.core.model.AssociationModel;
007    import de.deepamehta.core.model.AssociationDefinitionModel;
008    import de.deepamehta.core.osgi.PluginActivator;
009    import de.deepamehta.core.service.Directive;
010    import de.deepamehta.core.service.Directives;
011    import de.deepamehta.core.service.event.PostUpdateAssociationListener;
012    import de.deepamehta.core.service.event.PreDeleteAssociationListener;
013    
014    import java.util.logging.Logger;
015    
016    
017    
018    public class TypeEditorPlugin extends PluginActivator implements PostUpdateAssociationListener,
019                                                                     PreDeleteAssociationListener {
020    
021        // ---------------------------------------------------------------------------------------------- Instance Variables
022    
023        private Logger logger = Logger.getLogger(getClass().getName());
024    
025        // -------------------------------------------------------------------------------------------------- Public Methods
026    
027    
028    
029        // ********************************
030        // *** Listener Implementations ***
031        // ********************************
032    
033    
034    
035        @Override
036        public void postUpdateAssociation(Association assoc, AssociationModel oldModel) {
037            if (isAssocDef(assoc.getModel())) {
038                if (isAssocDef(oldModel)) {
039                    updateAssocDef(assoc);
040                } else {
041                    createAssocDef(assoc);
042                }
043            } else if (isAssocDef(oldModel)) {
044                removeAssocDef(assoc);
045            }
046        }
047    
048        // Note: we listen to the PRE event here, not the POST event. At POST time the assocdef sequence might be
049        // interrupted, which would result in a corrupted sequence once rebuild. (Due to the interruption, while
050        // rebuilding not all segments would be catched for deletion and recreated redundantly -> ambiguity.)
051        @Override
052        public void preDeleteAssociation(Association assoc) {
053            if (isAssocDef(assoc.getModel())) {
054                removeAssocDef(assoc);
055            }
056        }
057    
058    
059    
060        // ------------------------------------------------------------------------------------------------- Private Methods
061    
062        private void createAssocDef(Association assoc) {
063            Type parentType = fetchParentType(assoc);
064            String childTypeUri = fetchChildType(assoc).getUri();
065            // Note: the assoc def's ID is already known. Setting it explicitely
066            // prevents the core from creating the underlying association.
067            AssociationDefinitionModel assocDef = new AssociationDefinitionModel(
068                assoc.getId(), assoc.getUri(), assoc.getTypeUri(),
069                parentType.getUri(), childTypeUri, "dm4.core.one", "dm4.core.one",
070                null    // viewConfigModel=null
071            );
072            logger.info("### Adding association definition \"" + childTypeUri + "\" to type \"" + parentType.getUri() +
073                "\" (" + assocDef + ")");
074            //
075            parentType.addAssocDef(assocDef);
076            //
077            addUpdateTypeDirective(parentType);
078        }
079    
080        private void updateAssocDef(Association assoc) {
081            Type parentType = fetchParentType(assoc);
082            AssociationDefinitionModel assocDef = dms.getTypeStorage().fetchAssociationDefinition(assoc);
083            logger.info("### Updating association definition \"" + assocDef.getChildTypeUri() + "\" of type \"" +
084                parentType.getUri() + "\" (" + assocDef + ")");
085            //
086            parentType.updateAssocDef(assocDef);
087            //
088            addUpdateTypeDirective(parentType);
089        }
090    
091        private void removeAssocDef(Association assoc) {
092            Type parentType = fetchParentType(assoc);
093            String childTypeUri = fetchChildType(assoc).getUri();
094            logger.info("### Removing association definition \"" + childTypeUri + "\" from type \"" + parentType.getUri() +
095                "\"");
096            //
097            dms.getTypeStorage().removeAssociationDefinitionFromMemoryAndRebuildSequence(parentType, childTypeUri);
098            //
099            addUpdateTypeDirective(parentType);
100        }
101    
102    
103    
104        // === Helper ===
105    
106        private boolean isAssocDef(AssociationModel assoc) {
107            String typeUri = assoc.getTypeUri();
108            if (!typeUri.equals("dm4.core.aggregation_def") &&
109                !typeUri.equals("dm4.core.composition_def")) {
110                return false;
111            }
112            //
113            if (assoc.hasSameRoleTypeUris()) {
114                return false;
115            }
116            //
117            if (assoc.getRoleModel("dm4.core.parent_type") == null ||
118                assoc.getRoleModel("dm4.core.child_type") == null)  {
119                return false;
120            }
121            //
122            return true;
123        }
124    
125        // ### TODO: adding the UPDATE directive should be the responsibility of a type. The Type interface's
126        // ### addAssocDef(), updateAssocDef(), and removeAssocDef() methods should have a "directives" parameter.
127        private void addUpdateTypeDirective(Type type) {
128            if (type.getTypeUri().equals("dm4.core.topic_type")) {
129                Directives.get().add(Directive.UPDATE_TOPIC_TYPE, type);
130            } else if (type.getTypeUri().equals("dm4.core.assoc_type")) {
131                Directives.get().add(Directive.UPDATE_ASSOCIATION_TYPE, type);
132            }
133            // Note: no else here as error check already performed in fetchParentType()
134        }
135    
136        // ---
137    
138        private Type fetchParentType(Association assoc) {
139            Topic type = dms.getTypeStorage().fetchParentType(assoc);
140            String typeUri = type.getTypeUri();
141            if (typeUri.equals("dm4.core.topic_type")) {
142                return dms.getTopicType(type.getUri());
143            } else if (typeUri.equals("dm4.core.assoc_type")) {
144                return dms.getAssociationType(type.getUri());
145            } else {
146                throw new RuntimeException("Invalid association definition: the dm4.core.parent_type " +
147                    "player is not a type but of type \"" + typeUri + "\" (" + assoc + ")");
148            }
149        }
150    
151        private Topic fetchChildType(Association assoc) {
152            return dms.getTypeStorage().fetchChildType(assoc);
153        }
154    }