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.RelatedTopic; 007import de.deepamehta.core.Topic; 008import de.deepamehta.core.TopicType; 009import de.deepamehta.core.model.AssociationModel; 010import de.deepamehta.core.model.AssociationTypeModel; 011import de.deepamehta.core.model.RoleModel; 012import de.deepamehta.core.model.SimpleValue; 013import de.deepamehta.core.model.TopicModel; 014import de.deepamehta.core.model.TopicTypeModel; 015import de.deepamehta.core.service.CoreService; 016import de.deepamehta.core.service.DeepaMehtaEvent; 017import de.deepamehta.core.service.ModelFactory; 018import de.deepamehta.core.service.Plugin; 019import de.deepamehta.core.service.PluginInfo; 020import de.deepamehta.core.service.accesscontrol.AccessControl; 021import de.deepamehta.core.storage.spi.DeepaMehtaTransaction; 022 023import org.osgi.framework.BundleContext; 024 025import java.util.ArrayList; 026import java.util.List; 027import java.util.logging.Logger; 028 029 030 031/** 032 * Implementation of the DeepaMehta core service. Embeddable into Java applications. 033 */ 034public class CoreServiceImpl implements CoreService { 035 036 // ---------------------------------------------------------------------------------------------- Instance Variables 037 038 BundleContext bundleContext; 039 PersistenceLayer pl; 040 EventManager em; 041 ModelFactory mf; 042 MigrationManager migrationManager; 043 PluginManager pluginManager; 044 AccessControl accessControl; 045 WebPublishingService wpService; 046 047 private Logger logger = Logger.getLogger(getClass().getName()); 048 049 // ---------------------------------------------------------------------------------------------------- Constructors 050 051 /** 052 * @param bundleContext The context of the DeepaMehta 4 Core bundle. 053 */ 054 public CoreServiceImpl(PersistenceLayer pl, BundleContext bundleContext) { 055 this.bundleContext = bundleContext; 056 this.pl = pl; 057 this.em = pl.em; 058 this.mf = pl.mf; 059 this.migrationManager = new MigrationManager(this); 060 this.pluginManager = new PluginManager(this); 061 this.accessControl = new AccessControlImpl(pl); 062 this.wpService = new WebPublishingService(pl); 063 // 064 setupDB(); 065 } 066 067 // -------------------------------------------------------------------------------------------------- Public Methods 068 069 070 071 // ********************************** 072 // *** CoreService Implementation *** 073 // ********************************** 074 075 076 077 // === Topics === 078 079 @Override 080 public Topic getTopic(long topicId) { 081 return pl.getTopic(topicId); 082 } 083 084 @Override 085 public TopicImpl getTopicByUri(String uri) { 086 return pl.getTopicByUri(uri); 087 } 088 089 @Override 090 public Topic getTopicByValue(String key, SimpleValue value) { 091 return pl.getTopicByValue(key, value); 092 } 093 094 @Override 095 public List<Topic> getTopicsByValue(String key, SimpleValue value) { 096 return pl.getTopicsByValue(key, value); 097 } 098 099 @Override 100 public List<Topic> getTopicsByType(String topicTypeUri) { 101 return pl.getTopicsByType(topicTypeUri); 102 } 103 104 @Override 105 public List<Topic> searchTopics(String searchTerm, String fieldUri) { 106 return pl.searchTopics(searchTerm, fieldUri); 107 } 108 109 @Override 110 public Iterable<Topic> getAllTopics() { 111 return pl.getAllTopics(); 112 } 113 114 // --- 115 116 @Override 117 public TopicImpl createTopic(TopicModel model) { 118 return pl.createTopic(model); 119 } 120 121 @Override 122 public void updateTopic(TopicModel newModel) { 123 pl.updateTopic(newModel); 124 } 125 126 @Override 127 public void deleteTopic(long topicId) { 128 pl.deleteTopic(topicId); 129 } 130 131 132 133 // === Associations === 134 135 @Override 136 public Association getAssociation(long assocId) { 137 return pl.getAssociation(assocId); 138 } 139 140 @Override 141 public Association getAssociationByValue(String key, SimpleValue value) { 142 return pl.getAssociationByValue(key, value); 143 } 144 145 @Override 146 public List<Association> getAssociationsByValue(String key, SimpleValue value) { 147 return pl.getAssociationsByValue(key, value); 148 } 149 150 @Override 151 public Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, 152 String roleTypeUri1, String roleTypeUri2) { 153 return pl.getAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2); 154 } 155 156 @Override 157 public Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId, 158 String topicRoleTypeUri, String assocRoleTypeUri) { 159 return pl.getAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, topicRoleTypeUri, 160 assocRoleTypeUri); 161 } 162 163 // --- 164 165 @Override 166 public List<Association> getAssociationsByType(String assocTypeUri) { 167 return pl.getAssociationsByType(assocTypeUri); 168 } 169 170 @Override 171 public List<Association> getAssociations(long topic1Id, long topic2Id) { 172 return pl.getAssociations(topic1Id, topic2Id); 173 } 174 175 @Override 176 public List<Association> getAssociations(long topic1Id, long topic2Id, String assocTypeUri) { 177 return pl.getAssociations(topic1Id, topic2Id, assocTypeUri); 178 } 179 180 // --- 181 182 @Override 183 public Iterable<Association> getAllAssociations() { 184 return pl.getAllAssociations(); 185 } 186 187 @Override 188 public long[] getPlayerIds(long assocId) { 189 return pl.getPlayerIds(assocId); 190 } 191 192 // --- 193 194 @Override 195 public Association createAssociation(AssociationModel model) { 196 return pl.createAssociation((AssociationModelImpl) model); 197 } 198 199 @Override 200 public void updateAssociation(AssociationModel newModel) { 201 pl.updateAssociation(newModel); 202 } 203 204 @Override 205 public void deleteAssociation(long assocId) { 206 pl.deleteAssociation(assocId); 207 } 208 209 210 211 // === Topic Types === 212 213 @Override 214 public TopicType getTopicType(String uri) { 215 return pl.getTopicType(uri); 216 } 217 218 @Override 219 public TopicType getTopicTypeImplicitly(long topicId) { 220 return pl.getTopicTypeImplicitly(topicId); 221 } 222 223 // --- 224 225 @Override 226 public List<TopicType> getAllTopicTypes() { 227 return pl.getAllTopicTypes(); 228 } 229 230 // --- 231 232 @Override 233 public TopicType createTopicType(TopicTypeModel model) { 234 return pl.createTopicType((TopicTypeModelImpl) model); 235 } 236 237 @Override 238 public void updateTopicType(TopicTypeModel newModel) { 239 try { 240 // Note: type lookup is by ID. The URI might have changed, the ID does not. 241 // ### FIXME: access control 242 String topicTypeUri = pl.fetchTopic(newModel.getId()).getUri(); 243 pl.typeStorage.getTopicType(topicTypeUri).update(newModel); 244 } catch (Exception e) { 245 throw new RuntimeException("Updating topic type failed (" + newModel + ")", e); 246 } 247 } 248 249 @Override 250 public void deleteTopicType(String topicTypeUri) { 251 try { 252 pl.typeStorage.getTopicType(topicTypeUri).delete(); // ### TODO: delete view config topics 253 } catch (Exception e) { 254 throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e); 255 } 256 } 257 258 259 260 // === Association Types === 261 262 @Override 263 public AssociationType getAssociationType(String uri) { 264 return pl.getAssociationType(uri); 265 } 266 267 @Override 268 public AssociationType getAssociationTypeImplicitly(long assocId) { 269 return pl.getAssociationTypeImplicitly(assocId); 270 } 271 272 // --- 273 274 @Override 275 public List<AssociationType> getAllAssociationTypes() { 276 return pl.getAllAssociationTypes(); 277 } 278 279 // --- 280 281 @Override 282 public AssociationType createAssociationType(AssociationTypeModel model) { 283 return pl.createAssociationType((AssociationTypeModelImpl) model); 284 } 285 286 @Override 287 public void updateAssociationType(AssociationTypeModel newModel) { 288 try { 289 // Note: type lookup is by ID. The URI might have changed, the ID does not. 290 // ### FIXME: access control 291 String assocTypeUri = pl.fetchTopic(newModel.getId()).getUri(); 292 pl.typeStorage.getAssociationType(assocTypeUri).update(newModel); 293 } catch (Exception e) { 294 throw new RuntimeException("Updating association type failed (" + newModel + ")", e); 295 } 296 } 297 298 @Override 299 public void deleteAssociationType(String assocTypeUri) { 300 try { 301 pl.typeStorage.getAssociationType(assocTypeUri).delete(); 302 } catch (Exception e) { 303 throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e); 304 } 305 } 306 307 308 309 // === Role Types === 310 311 @Override 312 public Topic createRoleType(TopicModel model) { 313 return pl.createRoleType(model); 314 } 315 316 317 318 // === Generic Object === 319 320 @Override 321 public DeepaMehtaObject getObject(long id) { 322 return pl.getObject(id); 323 } 324 325 326 327 // === Plugins === 328 329 @Override 330 public PluginImpl getPlugin(String pluginUri) { 331 return pluginManager.getPlugin(pluginUri); 332 } 333 334 @Override 335 public List<PluginInfo> getPluginInfo() { 336 return pluginManager.getPluginInfo(); 337 } 338 339 340 341 // === Events === 342 343 @Override 344 public void fireEvent(DeepaMehtaEvent event, Object... params) { 345 em.fireEvent(event, params); 346 } 347 348 @Override 349 public void dispatchEvent(String pluginUri, DeepaMehtaEvent event, Object... params) { 350 em.dispatchEvent(getPlugin(pluginUri), event, params); 351 } 352 353 354 355 // === Properties === 356 357 @Override 358 public Object getProperty(long id, String propUri) { 359 return pl.fetchProperty(id, propUri); 360 } 361 362 @Override 363 public boolean hasProperty(long id, String propUri) { 364 return pl.hasProperty(id, propUri); 365 } 366 367 // --- 368 369 @Override 370 public List<Topic> getTopicsByProperty(String propUri, Object propValue) { 371 return pl.getTopicsByProperty(propUri, propValue); 372 } 373 374 @Override 375 public List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) { 376 return pl.getTopicsByPropertyRange(propUri, from, to); 377 } 378 379 @Override 380 public List<Association> getAssociationsByProperty(String propUri, Object propValue) { 381 return pl.getAssociationsByProperty(propUri, propValue); 382 } 383 384 @Override 385 public List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) { 386 return pl.getAssociationsByPropertyRange(propUri, from, to); 387 } 388 389 // --- 390 391 @Override 392 public void addTopicPropertyIndex(String propUri) { 393 int topics = 0; 394 int added = 0; 395 logger.info("########## Adding topic property index for \"" + propUri + "\""); 396 for (Topic topic : getAllTopics()) { 397 if (topic.hasProperty(propUri)) { 398 Object value = topic.getProperty(propUri); 399 pl.indexTopicProperty(topic.getId(), propUri, value); 400 added++; 401 } 402 topics++; 403 } 404 logger.info("########## Adding topic property index complete\n Topics processed: " + topics + 405 "\n added to index: " + added); 406 } 407 408 @Override 409 public void addAssociationPropertyIndex(String propUri) { 410 int assocs = 0; 411 int added = 0; 412 logger.info("########## Adding association property index for \"" + propUri + "\""); 413 for (Association assoc : getAllAssociations()) { 414 if (assoc.hasProperty(propUri)) { 415 Object value = assoc.getProperty(propUri); 416 pl.indexAssociationProperty(assoc.getId(), propUri, value); 417 added++; 418 } 419 assocs++; 420 } 421 logger.info("########## Adding association property complete\n Associations processed: " + assocs + 422 "\n added to index: " + added); 423 } 424 425 426 427 // === Misc === 428 429 @Override 430 public DeepaMehtaTransaction beginTx() { 431 return pl.beginTx(); 432 } 433 434 // --- 435 436 @Override 437 public ModelFactory getModelFactory() { 438 return mf; 439 } 440 441 @Override 442 public AccessControl getAccessControl() { 443 return accessControl; 444 } 445 446 @Override 447 public Object getDatabaseVendorObject() { 448 return pl.getDatabaseVendorObject(); 449 } 450 451 // ----------------------------------------------------------------------------------------- Package Private Methods 452 453 454 455 // === Helper === 456 457 /** 458 * Convenience method. 459 */ 460 Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) { 461 return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2)); 462 } 463 464 // ------------------------------------------------------------------------------------------------- Private Methods 465 466 467 468 // === Bootstrap === 469 470 /** 471 * Setups the database: 472 * 1) initializes the database. 473 * 2) in case of a clean install: sets up the bootstrap content. 474 * 3) runs the core migrations. 475 */ 476 private void setupDB() { 477 DeepaMehtaTransaction tx = beginTx(); 478 try { 479 logger.info("----- Setting up the database -----"); 480 boolean isCleanInstall = pl.init(); 481 if (isCleanInstall) { 482 setupBootstrapContent(); 483 } 484 migrationManager.runCoreMigrations(isCleanInstall); 485 tx.success(); 486 tx.finish(); 487 logger.info("----- Setting up the database complete -----"); 488 } catch (Exception e) { 489 logger.warning("ROLLBACK!"); 490 // Note: we don't put finish() in a finally clause here because 491 // in case of error the database has to be shut down. 492 tx.finish(); 493 pl.shutdown(); 494 throw new RuntimeException("Setting up the database failed", e); 495 } 496 } 497 498 private void setupBootstrapContent() { 499 try { 500 // Create meta types "Topic Type" and "Association Type" -- needed to create topic types and 501 // asscociation types 502 TopicModel t = mf.newTopicModel("dm4.core.topic_type", "dm4.core.meta_type", 503 new SimpleValue("Topic Type")); 504 TopicModel a = mf.newTopicModel("dm4.core.assoc_type", "dm4.core.meta_type", 505 new SimpleValue("Association Type")); 506 _createTopic(t); 507 _createTopic(a); 508 // Create topic types "Data Type" and "Role Type" 509 // ### Note: the topic type "Data Type" depends on the data type "Text" and the data type "Text" in turn 510 // depends on the topic type "Data Type". To resolve this circle we use a low-level (storage) call here 511 // and postpone the data type association. 512 TopicModel dataType = mf.newTopicTypeModel("dm4.core.data_type", "Data Type", "dm4.core.text"); 513 TopicModel roleType = mf.newTopicTypeModel("dm4.core.role_type", "Role Type", "dm4.core.text"); 514 _createTopic(dataType); 515 _createTopic(roleType); 516 // Create data type "Text" 517 TopicModel text = mf.newTopicModel("dm4.core.text", "dm4.core.data_type", new SimpleValue("Text")); 518 _createTopic(text); 519 // Create role types "Default", "Type", and "Instance" 520 TopicModel deflt = mf.newTopicModel("dm4.core.default", "dm4.core.role_type", new SimpleValue("Default")); 521 TopicModel type = mf.newTopicModel("dm4.core.type", "dm4.core.role_type", new SimpleValue("Type")); 522 TopicModel inst = mf.newTopicModel("dm4.core.instance", "dm4.core.role_type", new SimpleValue("Instance")); 523 _createTopic(deflt); 524 _createTopic(type); 525 _createTopic(inst); 526 // Create association type "Aggregation" -- needed to associate topic/association types with data types 527 TopicModel aggregation = mf.newAssociationTypeModel("dm4.core.aggregation", "Aggregation", "dm4.core.text"); 528 _createTopic(aggregation); 529 // Create association type "Instantiation" -- needed to associate topics with topic types 530 TopicModel instn = mf.newAssociationTypeModel("dm4.core.instantiation", "Instantiation", "dm4.core.text"); 531 _createTopic(instn); 532 // 533 // 1) Postponed topic type association 534 // 535 // Note: createTopicInstantiation() creates the associations by *low-level* (storage) calls. 536 // That's why the associations can be created *before* their type (here: "dm4.core.instantiation") 537 // is fully constructed (the type's data type is not yet associated => step 2). 538 pl.createTopicInstantiation(t.getId(), t.getTypeUri()); 539 pl.createTopicInstantiation(a.getId(), a.getTypeUri()); 540 pl.createTopicInstantiation(dataType.getId(), dataType.getTypeUri()); 541 pl.createTopicInstantiation(roleType.getId(), roleType.getTypeUri()); 542 pl.createTopicInstantiation(text.getId(), text.getTypeUri()); 543 pl.createTopicInstantiation(deflt.getId(), deflt.getTypeUri()); 544 pl.createTopicInstantiation(type.getId(), type.getTypeUri()); 545 pl.createTopicInstantiation(inst.getId(), inst.getTypeUri()); 546 pl.createTopicInstantiation(aggregation.getId(), aggregation.getTypeUri()); 547 pl.createTopicInstantiation(instn.getId(), instn.getTypeUri()); 548 // 549 // 2) Postponed data type association 550 // 551 // Note: associateDataType() creates the association by a *high-level* (service) call. 552 // This requires the association type (here: dm4.core.aggregation) to be fully constructed already. 553 // That's why the topic type associations (step 1) must be performed *before* the data type associations. 554 // ### FIXDOC: not true anymore 555 // 556 // Note: at time of the first associateDataType() call the required association type (dm4.core.aggregation) 557 // is *not* fully constructed yet! (it gets constructed through this very call). This works anyway because 558 // the data type assigning association is created *before* the association type is fetched. 559 // (see AssociationImpl.store(): storage.storeAssociation() is called before getType() 560 // in DeepaMehtaObjectImpl.store().) 561 // ### FIXDOC: not true anymore 562 // 563 // Important is that associateDataType("dm4.core.aggregation") is the first call here. 564 // ### FIXDOC: not true anymore 565 // 566 // Note: _associateDataType() creates the data type assigning association by a *low-level* (storage) call. 567 // A high-level (service) call would fail while setting the association's value. The involved getType() 568 // would fail (not because the association is missed -- it's created meanwhile, but) 569 // because this involves fetching the association including its value. The value doesn't exist yet, 570 // because its setting forms the begin of this vicious circle. 571 _associateDataType("dm4.core.meta_type", "dm4.core.text"); 572 _associateDataType("dm4.core.topic_type", "dm4.core.text"); 573 _associateDataType("dm4.core.assoc_type", "dm4.core.text"); 574 _associateDataType("dm4.core.data_type", "dm4.core.text"); 575 _associateDataType("dm4.core.role_type", "dm4.core.text"); 576 // 577 _associateDataType("dm4.core.aggregation", "dm4.core.text"); 578 _associateDataType("dm4.core.instantiation", "dm4.core.text"); 579 } catch (Exception e) { 580 throw new RuntimeException("Setting up the bootstrap content failed", e); 581 } 582 } 583 584 // --- 585 586 /** 587 * Low-level method that stores a topic without its "Instantiation" association. 588 * Needed for bootstrapping. 589 */ 590 private void _createTopic(TopicModel model) { 591 pl.storeTopic(model); 592 pl.storeTopicValue(model.getId(), model.getSimpleValue()); 593 } 594 595 /** 596 * Low-level method that stores an (data type) association without its "Instantiation" association. 597 * Needed for bootstrapping. 598 */ 599 private void _associateDataType(String typeUri, String dataTypeUri) { 600 AssociationModel assoc = mf.newAssociationModel("dm4.core.aggregation", 601 mf.newTopicRoleModel(typeUri, "dm4.core.type"), 602 mf.newTopicRoleModel(dataTypeUri, "dm4.core.default")); 603 pl.storeAssociation(assoc); 604 pl.storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 605 } 606}