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}