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