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