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 }