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}