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