001    package de.deepamehta.plugins.workspaces;
002    
003    import de.deepamehta.plugins.workspaces.service.WorkspacesService;
004    import de.deepamehta.plugins.facets.model.FacetValue;
005    import de.deepamehta.plugins.facets.service.FacetsService;
006    
007    import de.deepamehta.core.Association;
008    import de.deepamehta.core.AssociationType;
009    import de.deepamehta.core.DeepaMehtaObject;
010    import de.deepamehta.core.RelatedTopic;
011    import de.deepamehta.core.Topic;
012    import de.deepamehta.core.TopicType;
013    import de.deepamehta.core.Type;
014    import de.deepamehta.core.model.CompositeValueModel;
015    import de.deepamehta.core.model.SimpleValue;
016    import de.deepamehta.core.model.TopicModel;
017    import de.deepamehta.core.model.TopicReferenceModel;
018    import de.deepamehta.core.osgi.PluginActivator;
019    import de.deepamehta.core.service.ClientState;
020    import de.deepamehta.core.service.Directives;
021    import de.deepamehta.core.service.PluginService;
022    import de.deepamehta.core.service.annotation.ConsumesService;
023    import de.deepamehta.core.service.event.IntroduceAssociationTypeListener;
024    import de.deepamehta.core.service.event.IntroduceTopicTypeListener;
025    import de.deepamehta.core.service.event.PostCreateAssociationListener;
026    import de.deepamehta.core.service.event.PostCreateTopicListener;
027    
028    import java.util.ArrayList;
029    import java.util.List;
030    import java.util.logging.Logger;
031    
032    
033    
034    public class WorkspacesPlugin extends PluginActivator implements WorkspacesService, IntroduceTopicTypeListener,
035                                                                                        IntroduceAssociationTypeListener,
036                                                                                        PostCreateTopicListener,
037                                                                                        PostCreateAssociationListener {
038    
039        // ------------------------------------------------------------------------------------------------------- Constants
040    
041        private static final String DEFAULT_WORKSPACE_NAME = "DeepaMehta";
042        private static final String DEFAULT_WORKSPACE_URI = "de.workspaces.deepamehta";     // ### TODO: "dm4.workspaces..."
043    
044        // ---------------------------------------------------------------------------------------------- Instance Variables
045    
046        private FacetsService facetsService;
047    
048        private Logger logger = Logger.getLogger(getClass().getName());
049    
050        // -------------------------------------------------------------------------------------------------- Public Methods
051    
052    
053    
054        // ****************************************
055        // *** WorkspacesService Implementation ***
056        // ****************************************
057    
058    
059    
060        @Override
061        public List<RelatedTopic> getAssignedWorkspaces(DeepaMehtaObject object) {
062            return facetsService.getFacets(object, "dm4.workspaces.workspace_facet");
063        }
064    
065        @Override
066        public boolean isAssignedToWorkspace(Topic topic, long workspaceId) {
067            return facetsService.hasFacet(topic.getId(), "dm4.workspaces.workspace_facet", workspaceId);
068        }
069    
070        // ---
071    
072        @Override
073        public Topic getDefaultWorkspace() {
074            return fetchDefaultWorkspace();
075        }
076    
077        // ---
078    
079        @Override
080        public void assignToWorkspace(DeepaMehtaObject object, long workspaceId) {
081            checkArgument(workspaceId);
082            //
083            _assignToWorkspace(object, workspaceId);
084        }
085    
086        @Override
087        public void assignTypeToWorkspace(Type type, long workspaceId) {
088            checkArgument(workspaceId);
089            //
090            _assignToWorkspace(type, workspaceId);
091            for (Topic configTopic : type.getViewConfig().getConfigTopics()) {
092                _assignToWorkspace(configTopic, workspaceId);
093            }
094        }
095    
096        // ---
097    
098        @Override
099        public Topic createWorkspace(String name) {
100            return createWorkspace(name, null);
101        }
102    
103        @Override
104        public Topic createWorkspace(String name, String uri) {
105            logger.info("Creating workspace \"" + name + "\"");
106            return dms.createTopic(new TopicModel(uri, "dm4.workspaces.workspace", new CompositeValueModel()
107                .put("dm4.workspaces.name", name)
108            ), null);   // FIXME: clientState=null
109        }
110    
111    
112    
113        // ****************************
114        // *** Hook Implementations ***
115        // ****************************
116    
117    
118    
119        /**
120         * Creates the "Default" workspace.
121         */
122        @Override
123        public void postInstall() {
124            createWorkspace(DEFAULT_WORKSPACE_NAME, DEFAULT_WORKSPACE_URI);
125        }
126    
127        // ---
128    
129        @Override
130        @ConsumesService("de.deepamehta.plugins.facets.service.FacetsService")
131        public void serviceArrived(PluginService service) {
132            facetsService = (FacetsService) service;
133        }
134    
135        @Override
136        public void serviceGone(PluginService service) {
137            facetsService = null;
138        }
139    
140    
141    
142        // ********************************
143        // *** Listener Implementations ***
144        // ********************************
145    
146    
147    
148        @Override
149        public void introduceTopicType(TopicType topicType, ClientState clientState) {
150            long workspaceId = -1;
151            try {
152                workspaceId = workspaceIdForType(topicType, clientState);
153                if (workspaceId == -1) {
154                    return;
155                }
156                //
157                assignTypeToWorkspace(topicType, workspaceId);
158            } catch (Exception e) {
159                throw new RuntimeException("Assigning topic type \"" + topicType.getUri() + "\" to workspace " +
160                    workspaceId + " failed", e);
161            }
162        }
163    
164        @Override
165        public void introduceAssociationType(AssociationType assocType, ClientState clientState) {
166            long workspaceId = -1;
167            try {
168                workspaceId = workspaceIdForType(assocType, clientState);
169                if (workspaceId == -1) {
170                    return;
171                }
172                //
173                assignTypeToWorkspace(assocType, workspaceId);
174            } catch (Exception e) {
175                throw new RuntimeException("Assigning association type \"" + assocType.getUri() + "\" to workspace " +
176                    workspaceId + " failed", e);
177            }
178        }
179    
180        // ---
181    
182        /**
183         * Assigns every created topic to the current workspace.
184         */
185        @Override
186        public void postCreateTopic(Topic topic, ClientState clientState, Directives directives) {
187            long workspaceId = -1;
188            try {
189                // Note: we must avoid vicious circles
190                if (isWorkspacesPluginTopic(topic)) {
191                    return;
192                }
193                //
194                workspaceId = workspaceId(clientState);
195                // Note: when there is no current workspace (because no user is logged in) we do NOT fallback to assigning
196                // the default workspace. This would not help in gaining data consistency because the topics created so far
197                // (BEFORE the Workspaces plugin is activated) would still have no workspace assignment.
198                // Note: for types the situation is different. The type-introduction mechanism (see introduceTopicType()
199                // handler above) ensures EVERY type is catched (regardless of plugin activation order). For instances on
200                // the other hand we don't have such a mechanism (and don't want one either).
201                if (workspaceId == -1) {
202                    return;
203                }
204                //
205                assignToWorkspace(topic, workspaceId);
206            } catch (Exception e) {
207                throw new RuntimeException("Assigning topic " + topic.getId() + " to workspace " + workspaceId +
208                    " failed", e);
209            }
210        }
211    
212        /**
213         * Assigns every created association to the current workspace.
214         */
215        @Override
216        public void postCreateAssociation(Association assoc, ClientState clientState, Directives directives) {
217            long workspaceId = -1;
218            try {
219                workspaceId = workspaceId(clientState);
220                // Note: when there is no current workspace (because no user is logged in) we do NOT fallback to assigning
221                // the default workspace. This would not help in gaining data consistency because the associations created
222                // so far (BEFORE the Workspaces plugin is activated) would still have no workspace assignment.
223                // Note: for types the situation is different. The type-introduction mechanism (see introduceTopicType()
224                // handler above) ensures EVERY type is catched (regardless of plugin activation order). For instances on
225                // the other hand we don't have such a mechanism (and don't want one either).
226                if (workspaceId == -1) {
227                    return;
228                }
229                //
230                assignToWorkspace(assoc, workspaceId);
231            } catch (Exception e) {
232                throw new RuntimeException("Assigning association " + assoc.getId() + " to workspace " + workspaceId +
233                    " failed", e);
234            }
235        }
236    
237    
238    
239        // ------------------------------------------------------------------------------------------------- Private Methods
240    
241        private long workspaceId(ClientState clientState) {
242            if (clientState == null) {
243                return -1;
244            }
245            //
246            if (!clientState.has("dm4_workspace_id")) {
247                return -1;
248            }
249            //
250            return clientState.getLong("dm4_workspace_id");
251        }
252    
253        private long workspaceIdForType(Type type, ClientState clientState) {
254            long workspaceId = workspaceId(clientState);
255            if (workspaceId != -1) {
256                return workspaceId;
257            } else {
258                // assign types of the DeepaMehta standard distribution to the default workspace
259                if (isDeepaMehtaStandardType(type)) {
260                    Topic defaultWorkspace = fetchDefaultWorkspace();
261                    // Note: the default workspace is NOT required to exist ### TODO: think about it
262                    if (defaultWorkspace != null) {
263                        return defaultWorkspace.getId();
264                    }
265                }
266            }
267            return -1;
268        }
269    
270        // ---
271    
272        private void _assignToWorkspace(DeepaMehtaObject object, long workspaceId) {
273            // Note 1: we are refering to an existing workspace. So we must add a topic reference.
274            // Note 2: workspace_facet is a multi-facet. So we must call addRef() (as opposed to putRef()).
275            FacetValue value = new FacetValue("dm4.workspaces.workspace").addRef(workspaceId);
276            facetsService.updateFacet(object, "dm4.workspaces.workspace_facet", value, null, new Directives());
277            // clientState=null
278        }
279    
280        // --- Helper ---
281    
282        private boolean isDeepaMehtaStandardType(Type type) {
283            return type.getUri().startsWith("dm4.");
284        }
285    
286        private boolean isWorkspacesPluginTopic(Topic topic) {
287            return topic.getTypeUri().startsWith("dm4.workspaces.");
288        }
289    
290        // ---
291    
292        private Topic fetchDefaultWorkspace() {
293            return dms.getTopic("uri", new SimpleValue(DEFAULT_WORKSPACE_URI), false);      // fetchComposite=false
294        }
295    
296        /**
297         * Checks if the topic with the specified ID exists and is a Workspace. If not, an exception is thrown.
298         */
299        private void checkArgument(long topicId) {
300            String typeUri = dms.getTopic(topicId, false).getTypeUri();     // fetchComposite=false
301            if (!typeUri.equals("dm4.workspaces.workspace")) {
302                throw new IllegalArgumentException("Topic " + topicId + " is not a workspace (but of type \"" + typeUri +
303                    "\")");
304            }
305        }
306    }