001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.Association; 004import de.deepamehta.core.AssociationType; 005import de.deepamehta.core.DeepaMehtaObject; 006import de.deepamehta.core.RelatedAssociation; 007import de.deepamehta.core.RelatedTopic; 008import de.deepamehta.core.Topic; 009import de.deepamehta.core.TopicType; 010import de.deepamehta.core.model.AssociationModel; 011import de.deepamehta.core.model.AssociationTypeModel; 012import de.deepamehta.core.model.DeepaMehtaObjectModel; 013import de.deepamehta.core.model.RelatedAssociationModel; 014import de.deepamehta.core.model.RelatedTopicModel; 015import de.deepamehta.core.model.RoleModel; 016import de.deepamehta.core.model.SimpleValue; 017import de.deepamehta.core.model.TopicModel; 018import de.deepamehta.core.model.TopicTypeModel; 019import de.deepamehta.core.service.accesscontrol.AccessControlException; 020import de.deepamehta.core.storage.spi.DeepaMehtaStorage; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Iterator; 025import java.util.List; 026import java.util.logging.Logger; 027 028 029 030public class PersistenceLayer extends StorageDecorator { 031 032 // ------------------------------------------------------------------------------------------------------- Constants 033 034 private static final String URI_PREFIX_TOPIC_TYPE = "domain.project.topic_type_"; 035 private static final String URI_PREFIX_ASSOCIATION_TYPE = "domain.project.assoc_type_"; 036 private static final String URI_PREFIX_ROLE_TYPE = "domain.project.role_type_"; 037 038 // ---------------------------------------------------------------------------------------------- Instance Variables 039 040 TypeStorage typeStorage; 041 ValueStorage valueStorage; 042 043 EventManager em; 044 ModelFactoryImpl mf; 045 046 private final Logger logger = Logger.getLogger(getClass().getName()); 047 048 // ---------------------------------------------------------------------------------------------------- Constructors 049 050 public PersistenceLayer(DeepaMehtaStorage storage) { 051 super(storage); 052 // Note: mf must be initialzed before the type storage is instantiated 053 this.em = new EventManager(); 054 this.mf = (ModelFactoryImpl) storage.getModelFactory(); 055 // 056 this.typeStorage = new TypeStorage(this); 057 this.valueStorage = new ValueStorage(this); 058 // 059 // Note: this is a constructor side effect. This is a cyclic dependency. This is nasty. 060 // ### TODO: explain why we do it. 061 mf.pl = this; 062 // 063 bootstrapTypeCache(); 064 } 065 066 // ----------------------------------------------------------------------------------------- Package Private Methods 067 068 069 070 // === Topics === 071 072 Topic getTopic(long topicId) { 073 try { 074 return checkReadAccessAndInstantiate(fetchTopic(topicId)); 075 } catch (Exception e) { 076 throw new RuntimeException("Fetching topic " + topicId + " failed", e); 077 } 078 } 079 080 TopicImpl getTopicByUri(String uri) { 081 return getTopicByValue("uri", new SimpleValue(uri)); 082 } 083 084 TopicImpl getTopicByValue(String key, SimpleValue value) { 085 try { 086 TopicModelImpl topic = fetchTopic(key, value); 087 return topic != null ? this.<TopicImpl>checkReadAccessAndInstantiate(topic) : null; 088 // Note: inside a conditional operator the type witness is required (at least in Java 6) 089 } catch (Exception e) { 090 throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e); 091 } 092 } 093 094 List<Topic> getTopicsByValue(String key, SimpleValue value) { 095 try { 096 return checkReadAccessAndInstantiate(fetchTopics(key, value)); 097 } catch (Exception e) { 098 throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e); 099 } 100 } 101 102 List<Topic> getTopicsByType(String topicTypeUri) { 103 try { 104 return checkReadAccessAndInstantiate(typeStorage.getTopicType(topicTypeUri).getAllInstances()); 105 } catch (Exception e) { 106 throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e); 107 } 108 } 109 110 List<Topic> searchTopics(String searchTerm, String fieldUri) { 111 try { 112 return checkReadAccessAndInstantiate(queryTopics(fieldUri, new SimpleValue(searchTerm))); 113 } catch (Exception e) { 114 throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" + 115 fieldUri + "\")", e); 116 } 117 } 118 119 Iterable<Topic> getAllTopics() { 120 return new TopicIterable(this); 121 } 122 123 // --- 124 125 /** 126 * Convenience. 127 */ 128 TopicImpl createTopic(TopicModel model) { 129 return createTopic(model, null); // uriPrefix=null 130 } 131 132 /** 133 * Creates a new topic in the DB. 134 */ 135 TopicImpl createTopic(TopicModel model, String uriPrefix) { 136 try { 137 em.fireEvent(CoreEvent.PRE_CREATE_TOPIC, model); 138 // 139 // 1) store in DB 140 storeTopic(model); 141 valueStorage.storeValue((TopicModelImpl) model); 142 createTopicInstantiation(model.getId(), model.getTypeUri()); 143 // 2) set default URI 144 // If no URI is given the topic gets a default URI based on its ID, if requested. 145 // Note: this must be done *after* the topic is stored. The ID is not known before. 146 // Note: in case no URI was given: once stored a topic's URI is empty (not null). 147 if (uriPrefix != null && model.getUri().equals("")) { 148 ((TopicModelImpl) model).updateUri(uriPrefix + model.getId()); 149 } 150 // 3) instantiate 151 TopicImpl topic = new TopicImpl((TopicModelImpl) model, this); 152 // 153 em.fireEvent(CoreEvent.POST_CREATE_TOPIC, topic); 154 return topic; 155 } catch (Exception e) { 156 throw new RuntimeException("Creating topic " + model.getId() + " failed (typeUri=\"" + model.getTypeUri() + 157 "\")", e); 158 } 159 } 160 161 // --- 162 163 void updateTopic(TopicModel newModel) { 164 try { 165 TopicModelImpl model = fetchTopic(newModel.getId()); 166 model.update(newModel); 167 // 168 // Note: POST_UPDATE_TOPIC_REQUEST is fired only once per update request. 169 // On the other hand TopicModel's update() method is called multiple times while updating the child topics 170 // (see ChildTopicsModelImpl). 171 em.fireEvent(CoreEvent.POST_UPDATE_TOPIC_REQUEST, model.instantiate()); 172 } catch (Exception e) { 173 throw new RuntimeException("Updating topic " + newModel.getId() + " failed", e); 174 } 175 } 176 177 void deleteTopic(long topicId) { 178 fetchTopic(topicId).delete(); 179 } 180 181 182 183 // === Associations === 184 185 Association getAssociation(long assocId) { 186 try { 187 return checkReadAccessAndInstantiate(fetchAssociation(assocId)); 188 } catch (Exception e) { 189 throw new RuntimeException("Fetching association " + assocId + " failed", e); 190 } 191 } 192 193 Association getAssociationByValue(String key, SimpleValue value) { 194 try { 195 AssociationModelImpl assoc = fetchAssociation(key, value); 196 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 197 // Note: inside a conditional operator the type witness is required (at least in Java 6) 198 } catch (Exception e) { 199 throw new RuntimeException("Fetching association failed (key=\"" + key + "\", value=\"" + value + "\")", e); 200 } 201 } 202 203 List<Association> getAssociationsByValue(String key, SimpleValue value) { 204 try { 205 return checkReadAccessAndInstantiate(fetchAssociations(key, value)); 206 } catch (Exception e) { 207 throw new RuntimeException("Fetching associationss failed (key=\"" + key + "\", value=\"" + value + "\")", 208 e); 209 } 210 } 211 212 Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1, 213 String roleTypeUri2) { 214 String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + 215 ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\""; 216 try { 217 AssociationModelImpl assoc = fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2); 218 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 219 // Note: inside a conditional operator the type witness is required (at least in Java 6) 220 } catch (Exception e) { 221 throw new RuntimeException("Fetching association failed (" + info + ")", e); 222 } 223 } 224 225 Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId, 226 String topicRoleTypeUri, String assocRoleTypeUri) { 227 String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId + 228 ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\""; 229 logger.info(info); 230 try { 231 AssociationModelImpl assoc = fetchAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, 232 topicRoleTypeUri, assocRoleTypeUri); 233 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 234 // Note: inside a conditional operator the type witness is required (at least in Java 6) 235 } catch (Exception e) { 236 throw new RuntimeException("Fetching association failed (" + info + ")", e); 237 } 238 } 239 240 // --- 241 242 List<Association> getAssociationsByType(String assocTypeUri) { 243 try { 244 return checkReadAccessAndInstantiate(typeStorage.getAssociationType(assocTypeUri).getAllInstances()); 245 } catch (Exception e) { 246 throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")", 247 e); 248 } 249 } 250 251 List<Association> getAssociations(long topic1Id, long topic2Id) { 252 return getAssociations(topic1Id, topic2Id, null); 253 } 254 255 List<Association> getAssociations(long topic1Id, long topic2Id, String assocTypeUri) { 256 logger.info("topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + ", assocTypeUri=\"" + assocTypeUri + "\""); 257 try { 258 return checkReadAccessAndInstantiate(fetchAssociations(assocTypeUri, topic1Id, topic2Id, null, null)); 259 // roleTypeUri1=null, roleTypeUri2=null 260 } catch (Exception e) { 261 throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id + 262 " failed (assocTypeUri=\"" + assocTypeUri + "\")", e); 263 } 264 } 265 266 // --- 267 268 Iterable<Association> getAllAssociations() { 269 return new AssociationIterable(this); 270 } 271 272 long[] getPlayerIds(long assocId) { 273 return fetchPlayerIds(assocId); 274 } 275 276 // --- 277 278 /** 279 * Convenience. 280 */ 281 Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) { 282 return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2)); 283 } 284 285 /** 286 * Creates a new association in the DB. 287 */ 288 Association createAssociation(AssociationModelImpl model) { 289 try { 290 em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model); 291 // 292 // 1) store in DB 293 storeAssociation(model); 294 valueStorage.storeValue(model); 295 createAssociationInstantiation(model.getId(), model.getTypeUri()); 296 // 2) instantiate 297 Association assoc = new AssociationImpl(model, this); 298 // 299 em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc); 300 return assoc; 301 } catch (Exception e) { 302 throw new RuntimeException("Creating association failed (" + model + ")", e); 303 } 304 } 305 306 // --- 307 308 void updateAssociation(AssociationModel newModel) { 309 try { 310 AssociationModelImpl model = fetchAssociation(newModel.getId()); 311 model.update(newModel); 312 // 313 // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()). 314 // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated. 315 // Its childs are always topics (never associations). 316 } catch (Exception e) { 317 throw new RuntimeException("Updating association " + newModel.getId() + " failed", e); 318 } 319 } 320 321 void deleteAssociation(long assocId) { 322 fetchAssociation(assocId).delete(); 323 } 324 325 326 327 // === 328 329 void createTopicInstantiation(long topicId, String topicTypeUri) { 330 try { 331 AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation", 332 mf.newTopicRoleModel(topicTypeUri, "dm4.core.type"), 333 mf.newTopicRoleModel(topicId, "dm4.core.instance")); 334 storeAssociation(assoc); // direct storage calls used here ### explain 335 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 336 createAssociationInstantiation(assoc.getId(), assoc.getTypeUri()); 337 } catch (Exception e) { 338 throw new RuntimeException("Associating topic " + topicId + 339 " with topic type \"" + topicTypeUri + "\" failed", e); 340 } 341 } 342 343 void createAssociationInstantiation(long assocId, String assocTypeUri) { 344 try { 345 AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation", 346 mf.newTopicRoleModel(assocTypeUri, "dm4.core.type"), 347 mf.newAssociationRoleModel(assocId, "dm4.core.instance")); 348 storeAssociation(assoc); // direct storage calls used here ### explain 349 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 350 } catch (Exception e) { 351 throw new RuntimeException("Associating association " + assocId + 352 " with association type \"" + assocTypeUri + "\" failed", e); 353 } 354 } 355 356 357 358 // === Types === 359 360 TopicType getTopicType(String uri) { 361 return typeStorage.getTopicType(uri).instantiate(); 362 } 363 364 AssociationType getAssociationType(String uri) { 365 return typeStorage.getAssociationType(uri).instantiate(); 366 } 367 368 // --- 369 370 TopicType createTopicType(TopicTypeModelImpl model) { 371 try { 372 // store in DB 373 createTopic(model, URI_PREFIX_TOPIC_TYPE); // create generic topic 374 typeStorage.storeType(model); // store type-specific parts 375 // 376 TopicType topicType = model.instantiate(); 377 em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType); 378 // 379 return topicType; 380 } catch (Exception e) { 381 throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed (" + model + ")", e); 382 } 383 } 384 385 AssociationType createAssociationType(AssociationTypeModelImpl model) { 386 try { 387 // store in DB 388 createTopic(model, URI_PREFIX_ASSOCIATION_TYPE); // create generic topic 389 typeStorage.storeType(model); // store type-specific parts 390 // 391 AssociationType assocType = model.instantiate(); 392 em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType); 393 // 394 return assocType; 395 } catch (Exception e) { 396 throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed (" + model + ")", 397 e); 398 } 399 } 400 401 // --- 402 403 Topic createRoleType(TopicModel model) { 404 // check type URI argument 405 String typeUri = model.getTypeUri(); 406 if (typeUri == null) { 407 model.setTypeUri("dm4.core.role_type"); 408 } else { 409 if (!typeUri.equals("dm4.core.role_type")) { 410 throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " + 411 "(found: \"" + typeUri + "\")"); 412 } 413 } 414 // 415 return createTopic(model, URI_PREFIX_ROLE_TYPE); 416 } 417 418 419 420 // === Generic Object === 421 422 DeepaMehtaObject getObject(long id) { 423 DeepaMehtaObjectModelImpl model = fetchObject(id); 424 checkReadAccess(model); 425 return model.instantiate(); 426 } 427 428 429 430 // === Properties === 431 432 List<Topic> getTopicsByProperty(String propUri, Object propValue) { 433 return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue)); 434 } 435 436 List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) { 437 return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to)); 438 } 439 440 List<Association> getAssociationsByProperty(String propUri, Object propValue) { 441 return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue)); 442 } 443 444 List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) { 445 return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to)); 446 } 447 448 449 450 // === Access Control / Instantiation === 451 452 // These methods 1) instantiate objects from models, and 2) check the READ permission for each model. 453 // Call these methods when passing objects fetched from the DB to the user. 454 // ### TODO: make these private? 455 456 <O> O checkReadAccessAndInstantiate(DeepaMehtaObjectModelImpl model) { 457 checkReadAccess(model); 458 return (O) model.instantiate(); 459 } 460 461 <O> List<O> checkReadAccessAndInstantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) { 462 filterReadables(models); 463 return instantiate(models); 464 } 465 466 // --- 467 468 private void filterReadables(Iterable<? extends DeepaMehtaObjectModelImpl> models) { 469 Iterator<? extends DeepaMehtaObjectModelImpl> i = models.iterator(); 470 while (i.hasNext()) { 471 DeepaMehtaObjectModelImpl model = i.next(); 472 try { 473 checkReadAccess(model); 474 } catch (AccessControlException e) { 475 i.remove(); 476 } 477 } 478 } 479 480 /** 481 * @throws AccessControlException 482 */ 483 private void checkReadAccess(DeepaMehtaObjectModelImpl model) { 484 em.fireEvent(model.getPreGetEvent(), model.getId()); 485 } 486 487 488 489 // === Instantiation === 490 491 <O> List<O> instantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) { 492 List<O> objects = new ArrayList(); 493 for (DeepaMehtaObjectModelImpl model : models) { 494 objects.add((O) model.instantiate()); 495 } 496 return objects; 497 } 498 499 500 501 // === 502 503 private void bootstrapTypeCache() { 504 TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dm4.core.meta_meta_type", "Meta Meta Type", 505 "dm4.core.text"); 506 metaMetaType.setTypeUri("dm4.core.meta_meta_meta_type"); 507 typeStorage.putInTypeCache(metaMetaType); 508 } 509}