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}