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 List<String> getTopicTypeUris() { 215 try { 216 Topic metaType = pl.checkReadAccessAndInstantiate(pl.fetchTopic("uri", 217 new SimpleValue("dm4.core.topic_type"))); // ### TODO: rethink access control 218 List<RelatedTopic> topicTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type", 219 "dm4.core.instance", "dm4.core.topic_type"); // ### TODO: perform by-value search instead 220 List<String> topicTypeUris = new ArrayList(); 221 // add meta types 222 topicTypeUris.add("dm4.core.topic_type"); 223 topicTypeUris.add("dm4.core.assoc_type"); 224 topicTypeUris.add("dm4.core.meta_type"); 225 topicTypeUris.add("dm4.core.meta_meta_type"); 226 // add regular types 227 for (Topic topicType : topicTypes) { 228 topicTypeUris.add(topicType.getUri()); 229 } 230 return topicTypeUris; 231 } catch (Exception e) { 232 throw new RuntimeException("Fetching list of topic type URIs failed", e); 233 } 234 } 235 236 @Override 237 public TopicType getTopicType(String uri) { 238 return pl.getTopicType(uri); 239 } 240 241 @Override 242 public List<TopicType> getAllTopicTypes() { 243 try { 244 List<TopicType> topicTypes = new ArrayList(); 245 for (String uri : getTopicTypeUris()) { 246 TopicType topicType = getTopicType(uri); 247 topicTypes.add(topicType); 248 } 249 return topicTypes; 250 } catch (Exception e) { 251 throw new RuntimeException("Fetching all topic types failed", e); 252 } 253 } 254 255 // --- 256 257 @Override 258 public TopicType createTopicType(TopicTypeModel model) { 259 return pl.createTopicType((TopicTypeModelImpl) model); 260 } 261 262 @Override 263 public void updateTopicType(TopicTypeModel newModel) { 264 try { 265 // Note: type lookup is by ID. The URI might have changed, the ID does not. 266 // ### FIXME: access control 267 String topicTypeUri = pl.fetchTopic(newModel.getId()).getUri(); 268 pl.typeStorage.getTopicType(topicTypeUri).update(newModel); 269 } catch (Exception e) { 270 throw new RuntimeException("Updating topic type failed (" + newModel + ")", e); 271 } 272 } 273 274 @Override 275 public void deleteTopicType(String topicTypeUri) { 276 try { 277 pl.typeStorage.getTopicType(topicTypeUri).delete(); // ### TODO: delete view config topics 278 } catch (Exception e) { 279 throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e); 280 } 281 } 282 283 284 285 // === Association Types === 286 287 @Override 288 public List<String> getAssociationTypeUris() { 289 try { 290 Topic metaType = pl.checkReadAccessAndInstantiate(pl.fetchTopic("uri", 291 new SimpleValue("dm4.core.assoc_type"))); // ### TODO: rethink access control 292 List<RelatedTopic> assocTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type", 293 "dm4.core.instance", "dm4.core.assoc_type"); // ### TODO: perform by-value search instead 294 List<String> assocTypeUris = new ArrayList(); 295 for (Topic assocType : assocTypes) { 296 assocTypeUris.add(assocType.getUri()); 297 } 298 return assocTypeUris; 299 } catch (Exception e) { 300 throw new RuntimeException("Fetching list of association type URIs failed", e); 301 } 302 } 303 304 @Override 305 public AssociationType getAssociationType(String uri) { 306 return pl.getAssociationType(uri); 307 } 308 309 @Override 310 public List<AssociationType> getAllAssociationTypes() { 311 try { 312 List<AssociationType> assocTypes = new ArrayList(); 313 for (String uri : getAssociationTypeUris()) { 314 AssociationType assocType = getAssociationType(uri); 315 assocTypes.add(assocType); 316 } 317 return assocTypes; 318 } catch (Exception e) { 319 throw new RuntimeException("Fetching all association types failed", e); 320 } 321 } 322 323 // --- 324 325 @Override 326 public AssociationType createAssociationType(AssociationTypeModel model) { 327 return pl.createAssociationType((AssociationTypeModelImpl) model); 328 } 329 330 @Override 331 public void updateAssociationType(AssociationTypeModel newModel) { 332 try { 333 // Note: type lookup is by ID. The URI might have changed, the ID does not. 334 // ### FIXME: access control 335 String assocTypeUri = pl.fetchTopic(newModel.getId()).getUri(); 336 pl.typeStorage.getAssociationType(assocTypeUri).update(newModel); 337 } catch (Exception e) { 338 throw new RuntimeException("Updating association type failed (" + newModel + ")", e); 339 } 340 } 341 342 @Override 343 public void deleteAssociationType(String assocTypeUri) { 344 try { 345 pl.typeStorage.getAssociationType(assocTypeUri).delete(); 346 } catch (Exception e) { 347 throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e); 348 } 349 } 350 351 352 353 // === Role Types === 354 355 @Override 356 public Topic createRoleType(TopicModel model) { 357 return pl.createRoleType(model); 358 } 359 360 361 362 // === Generic Object === 363 364 @Override 365 public DeepaMehtaObject getObject(long id) { 366 return pl.getObject(id); 367 } 368 369 370 371 // === Plugins === 372 373 @Override 374 public PluginImpl getPlugin(String pluginUri) { 375 return pluginManager.getPlugin(pluginUri); 376 } 377 378 @Override 379 public List<PluginInfo> getPluginInfo() { 380 return pluginManager.getPluginInfo(); 381 } 382 383 384 385 // === Events === 386 387 @Override 388 public void fireEvent(DeepaMehtaEvent event, Object... params) { 389 em.fireEvent(event, params); 390 } 391 392 @Override 393 public void dispatchEvent(String pluginUri, DeepaMehtaEvent event, Object... params) { 394 em.dispatchEvent(getPlugin(pluginUri), event, params); 395 } 396 397 398 399 // === Properties === 400 401 @Override 402 public Object getProperty(long id, String propUri) { 403 return pl.fetchProperty(id, propUri); 404 } 405 406 @Override 407 public boolean hasProperty(long id, String propUri) { 408 return pl.hasProperty(id, propUri); 409 } 410 411 // --- 412 413 @Override 414 public List<Topic> getTopicsByProperty(String propUri, Object propValue) { 415 return pl.getTopicsByProperty(propUri, propValue); 416 } 417 418 @Override 419 public List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) { 420 return pl.getTopicsByPropertyRange(propUri, from, to); 421 } 422 423 @Override 424 public List<Association> getAssociationsByProperty(String propUri, Object propValue) { 425 return pl.getAssociationsByProperty(propUri, propValue); 426 } 427 428 @Override 429 public List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) { 430 return pl.getAssociationsByPropertyRange(propUri, from, to); 431 } 432 433 // --- 434 435 @Override 436 public void addTopicPropertyIndex(String propUri) { 437 int topics = 0; 438 int added = 0; 439 logger.info("########## Adding topic property index for \"" + propUri + "\""); 440 for (Topic topic : getAllTopics()) { 441 if (topic.hasProperty(propUri)) { 442 Object value = topic.getProperty(propUri); 443 pl.indexTopicProperty(topic.getId(), propUri, value); 444 added++; 445 } 446 topics++; 447 } 448 logger.info("########## Adding topic property index complete\n Topics processed: " + topics + 449 "\n added to index: " + added); 450 } 451 452 @Override 453 public void addAssociationPropertyIndex(String propUri) { 454 int assocs = 0; 455 int added = 0; 456 logger.info("########## Adding association property index for \"" + propUri + "\""); 457 for (Association assoc : getAllAssociations()) { 458 if (assoc.hasProperty(propUri)) { 459 Object value = assoc.getProperty(propUri); 460 pl.indexAssociationProperty(assoc.getId(), propUri, value); 461 added++; 462 } 463 assocs++; 464 } 465 logger.info("########## Adding association property complete\n Associations processed: " + assocs + 466 "\n added to index: " + added); 467 } 468 469 470 471 // === Misc === 472 473 @Override 474 public DeepaMehtaTransaction beginTx() { 475 return pl.beginTx(); 476 } 477 478 // --- 479 480 @Override 481 public ModelFactory getModelFactory() { 482 return mf; 483 } 484 485 @Override 486 public AccessControl getAccessControl() { 487 return accessControl; 488 } 489 490 @Override 491 public Object getDatabaseVendorObject() { 492 return pl.getDatabaseVendorObject(); 493 } 494 495 // ----------------------------------------------------------------------------------------- Package Private Methods 496 497 498 499 // === Helper === 500 501 /** 502 * Convenience method. 503 */ 504 Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) { 505 return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2)); 506 } 507 508 // ------------------------------------------------------------------------------------------------- Private Methods 509 510 511 512 // === Bootstrap === 513 514 /** 515 * Setups the database: 516 * 1) initializes the database. 517 * 2) in case of a clean install: sets up the bootstrap content. 518 * 3) runs the core migrations. 519 */ 520 private void setupDB() { 521 DeepaMehtaTransaction tx = beginTx(); 522 try { 523 logger.info("----- Setting up the database -----"); 524 boolean isCleanInstall = pl.init(); 525 if (isCleanInstall) { 526 setupBootstrapContent(); 527 } 528 migrationManager.runCoreMigrations(isCleanInstall); 529 tx.success(); 530 tx.finish(); 531 logger.info("----- Setting up the database complete -----"); 532 } catch (Exception e) { 533 logger.warning("ROLLBACK!"); 534 // Note: we don't put finish() in a finally clause here because 535 // in case of error the database has to be shut down. 536 tx.finish(); 537 pl.shutdown(); 538 throw new RuntimeException("Setting up the database failed", e); 539 } 540 } 541 542 private void setupBootstrapContent() { 543 try { 544 // Create meta types "Topic Type" and "Association Type" -- needed to create topic types and 545 // asscociation types 546 TopicModel t = mf.newTopicModel("dm4.core.topic_type", "dm4.core.meta_type", 547 new SimpleValue("Topic Type")); 548 TopicModel a = mf.newTopicModel("dm4.core.assoc_type", "dm4.core.meta_type", 549 new SimpleValue("Association Type")); 550 _createTopic(t); 551 _createTopic(a); 552 // Create topic types "Data Type" and "Role Type" 553 // ### Note: the topic type "Data Type" depends on the data type "Text" and the data type "Text" in turn 554 // depends on the topic type "Data Type". To resolve this circle we use a low-level (storage) call here 555 // and postpone the data type association. 556 TopicModel dataType = mf.newTopicTypeModel("dm4.core.data_type", "Data Type", "dm4.core.text"); 557 TopicModel roleType = mf.newTopicTypeModel("dm4.core.role_type", "Role Type", "dm4.core.text"); 558 _createTopic(dataType); 559 _createTopic(roleType); 560 // Create data type "Text" 561 TopicModel text = mf.newTopicModel("dm4.core.text", "dm4.core.data_type", new SimpleValue("Text")); 562 _createTopic(text); 563 // Create role types "Default", "Type", and "Instance" 564 TopicModel deflt = mf.newTopicModel("dm4.core.default", "dm4.core.role_type", new SimpleValue("Default")); 565 TopicModel type = mf.newTopicModel("dm4.core.type", "dm4.core.role_type", new SimpleValue("Type")); 566 TopicModel inst = mf.newTopicModel("dm4.core.instance", "dm4.core.role_type", new SimpleValue("Instance")); 567 _createTopic(deflt); 568 _createTopic(type); 569 _createTopic(inst); 570 // Create association type "Aggregation" -- needed to associate topic/association types with data types 571 TopicModel aggregation = mf.newAssociationTypeModel("dm4.core.aggregation", "Aggregation", "dm4.core.text"); 572 _createTopic(aggregation); 573 // Create association type "Instantiation" -- needed to associate topics with topic types 574 TopicModel instn = mf.newAssociationTypeModel("dm4.core.instantiation", "Instantiation", "dm4.core.text"); 575 _createTopic(instn); 576 // 577 // 1) Postponed topic type association 578 // 579 // Note: createTopicInstantiation() creates the associations by *low-level* (storage) calls. 580 // That's why the associations can be created *before* their type (here: "dm4.core.instantiation") 581 // is fully constructed (the type's data type is not yet associated => step 2). 582 pl.createTopicInstantiation(t.getId(), t.getTypeUri()); 583 pl.createTopicInstantiation(a.getId(), a.getTypeUri()); 584 pl.createTopicInstantiation(dataType.getId(), dataType.getTypeUri()); 585 pl.createTopicInstantiation(roleType.getId(), roleType.getTypeUri()); 586 pl.createTopicInstantiation(text.getId(), text.getTypeUri()); 587 pl.createTopicInstantiation(deflt.getId(), deflt.getTypeUri()); 588 pl.createTopicInstantiation(type.getId(), type.getTypeUri()); 589 pl.createTopicInstantiation(inst.getId(), inst.getTypeUri()); 590 pl.createTopicInstantiation(aggregation.getId(), aggregation.getTypeUri()); 591 pl.createTopicInstantiation(instn.getId(), instn.getTypeUri()); 592 // 593 // 2) Postponed data type association 594 // 595 // Note: associateDataType() creates the association by a *high-level* (service) call. 596 // This requires the association type (here: dm4.core.aggregation) to be fully constructed already. 597 // That's why the topic type associations (step 1) must be performed *before* the data type associations. 598 // ### FIXDOC: not true anymore 599 // 600 // Note: at time of the first associateDataType() call the required association type (dm4.core.aggregation) 601 // is *not* fully constructed yet! (it gets constructed through this very call). This works anyway because 602 // the data type assigning association is created *before* the association type is fetched. 603 // (see AssociationImpl.store(): storage.storeAssociation() is called before getType() 604 // in DeepaMehtaObjectImpl.store().) 605 // ### FIXDOC: not true anymore 606 // 607 // Important is that associateDataType("dm4.core.aggregation") is the first call here. 608 // ### FIXDOC: not true anymore 609 // 610 // Note: _associateDataType() creates the data type assigning association by a *low-level* (storage) call. 611 // A high-level (service) call would fail while setting the association's value. The involved getType() 612 // would fail (not because the association is missed -- it's created meanwhile, but) 613 // because this involves fetching the association including its value. The value doesn't exist yet, 614 // because its setting forms the begin of this vicious circle. 615 _associateDataType("dm4.core.meta_type", "dm4.core.text"); 616 _associateDataType("dm4.core.topic_type", "dm4.core.text"); 617 _associateDataType("dm4.core.assoc_type", "dm4.core.text"); 618 _associateDataType("dm4.core.data_type", "dm4.core.text"); 619 _associateDataType("dm4.core.role_type", "dm4.core.text"); 620 // 621 _associateDataType("dm4.core.aggregation", "dm4.core.text"); 622 _associateDataType("dm4.core.instantiation", "dm4.core.text"); 623 } catch (Exception e) { 624 throw new RuntimeException("Setting up the bootstrap content failed", e); 625 } 626 } 627 628 // --- 629 630 /** 631 * Low-level method that stores a topic without its "Instantiation" association. 632 * Needed for bootstrapping. 633 */ 634 private void _createTopic(TopicModel model) { 635 pl.storeTopic(model); 636 pl.storeTopicValue(model.getId(), model.getSimpleValue()); 637 } 638 639 /** 640 * Low-level method that stores an (data type) association without its "Instantiation" association. 641 * Needed for bootstrapping. 642 */ 643 private void _associateDataType(String typeUri, String dataTypeUri) { 644 AssociationModel assoc = mf.newAssociationModel("dm4.core.aggregation", 645 mf.newTopicRoleModel(typeUri, "dm4.core.type"), 646 mf.newTopicRoleModel(dataTypeUri, "dm4.core.default")); 647 pl.storeAssociation(assoc); 648 pl.storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 649 } 650}