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 }