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