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 }