001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationDefinition;
005    import de.deepamehta.core.AssociationType;
006    import de.deepamehta.core.RelatedAssociation;
007    import de.deepamehta.core.RelatedTopic;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.TopicType;
010    import de.deepamehta.core.Type;
011    import de.deepamehta.core.model.AssociationModel;
012    import de.deepamehta.core.model.AssociationRoleModel;
013    import de.deepamehta.core.model.AssociationTypeModel;
014    import de.deepamehta.core.model.DeepaMehtaObjectModel;
015    import de.deepamehta.core.model.RelatedAssociationModel;
016    import de.deepamehta.core.model.RelatedTopicModel;
017    import de.deepamehta.core.model.RoleModel;
018    import de.deepamehta.core.model.SimpleValue;
019    import de.deepamehta.core.model.TopicModel;
020    import de.deepamehta.core.model.TopicRoleModel;
021    import de.deepamehta.core.model.TopicTypeModel;
022    import de.deepamehta.core.service.ClientState;
023    import de.deepamehta.core.service.DeepaMehtaEvent;
024    import de.deepamehta.core.service.DeepaMehtaService;
025    import de.deepamehta.core.service.Directives;
026    import de.deepamehta.core.service.Plugin;
027    import de.deepamehta.core.service.PluginInfo;
028    import de.deepamehta.core.service.ResultList;
029    import de.deepamehta.core.service.TypeStorage;
030    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
031    
032    import org.osgi.framework.BundleContext;
033    
034    import java.util.ArrayList;
035    import java.util.List;
036    import java.util.logging.Logger;
037    
038    
039    
040    /**
041     * Implementation of the DeepaMehta core service. Embeddable into Java applications.
042     */
043    public class EmbeddedService implements DeepaMehtaService {
044    
045        // ------------------------------------------------------------------------------------------------------- Constants
046    
047        private static final String DEFAULT_TOPIC_TYPE_URI = "domain.project.topic_type_";
048        private static final String DEFAULT_ASSOCIATION_TYPE_URI = "domain.project.assoc_type_";
049    
050        // ---------------------------------------------------------------------------------------------- Instance Variables
051    
052        StorageDecorator storageDecorator;
053        BundleContext bundleContext;
054        MigrationManager migrationManager;
055        PluginManager pluginManager;
056        EventManager eventManager;
057        TypeCache typeCache;
058        TypeStorageImpl typeStorage;
059        ValueStorage valueStorage;
060    
061        private Logger logger = Logger.getLogger(getClass().getName());
062    
063        // ---------------------------------------------------------------------------------------------------- Constructors
064    
065        /**
066         * @param   bundleContext   The context of the DeepaMehta 4 Core bundle.
067         */
068        public EmbeddedService(StorageDecorator storageDecorator, BundleContext bundleContext) {
069            this.storageDecorator = storageDecorator;
070            this.bundleContext = bundleContext;
071            this.migrationManager = new MigrationManager(this);
072            this.pluginManager = new PluginManager(this);
073            this.eventManager = new EventManager(this);
074            this.typeCache = new TypeCache(this);
075            this.typeStorage = new TypeStorageImpl(this);
076            this.valueStorage = new ValueStorage(this);
077            bootstrapTypeCache();
078            setupDB();
079        }
080    
081        // -------------------------------------------------------------------------------------------------- Public Methods
082    
083    
084    
085        // ****************************************
086        // *** DeepaMehtaService Implementation ***
087        // ****************************************
088    
089    
090    
091        // === Topics ===
092    
093        @Override
094        public Topic getTopic(long topicId, boolean fetchComposite) {
095            try {
096                return instantiateTopic(storageDecorator.fetchTopic(topicId), fetchComposite);
097            } catch (Exception e) {
098                throw new RuntimeException("Fetching topic " + topicId + " failed", e);
099            }
100        }
101    
102        @Override
103        public Topic getTopic(String key, SimpleValue value, boolean fetchComposite) {
104            try {
105                TopicModel topic = storageDecorator.fetchTopic(key, value);
106                return topic != null ? instantiateTopic(topic, fetchComposite) : null;
107            } catch (Exception e) {
108                throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e);
109            }
110        }
111    
112        @Override
113        public List<Topic> getTopics(String key, SimpleValue value, boolean fetchComposite) {
114            try {
115                return instantiateTopics(storageDecorator.fetchTopics(key, value), fetchComposite);
116            } catch (Exception e) {
117                throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e);
118            }
119        }
120    
121        @Override
122        public ResultList<RelatedTopic> getTopics(String topicTypeUri, boolean fetchComposite, int maxResultSize) {
123            try {
124                return getTopicType(topicTypeUri).getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
125                    "dm4.core.instance", topicTypeUri, fetchComposite, false, maxResultSize);
126            } catch (Exception e) {
127                throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e);
128            }
129        }
130    
131        @Override
132        public List<Topic> searchTopics(String searchTerm, String fieldUri) {
133            try {
134                // ### FIXME: fetchComposite=false, parameterize it
135                return instantiateTopics(storageDecorator.queryTopics(searchTerm, fieldUri), false);
136            } catch (Exception e) {
137                throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" +
138                    fieldUri + "\")", e);
139            }
140        }
141    
142        @Override
143        public Iterable<Topic> getAllTopics() {
144            return new TopicIterable(this);
145        }
146    
147        // ---
148    
149        @Override
150        public Topic createTopic(TopicModel model, ClientState clientState) {
151            DeepaMehtaTransaction tx = beginTx();
152            try {
153                fireEvent(CoreEvent.PRE_CREATE_TOPIC, model, clientState);
154                //
155                Directives directives = new Directives();   // ### FIXME: directives are ignored
156                Topic topic = topicFactory(model, clientState, directives);
157                //
158                fireEvent(CoreEvent.POST_CREATE_TOPIC, topic, clientState, directives);
159                //
160                tx.success();
161                return topic;
162            } catch (Exception e) {
163                logger.warning("ROLLBACK!");
164                throw new RuntimeException("Creating topic failed (" + model + ")", e);
165            } finally {
166                tx.finish();
167            }
168        }
169    
170        @Override
171        public Directives updateTopic(TopicModel model, ClientState clientState) {
172            DeepaMehtaTransaction tx = beginTx();
173            try {
174                Directives directives = new Directives();
175                //
176                getTopic(model.getId(), true).update(model, clientState, directives);   // fetchComposite=true
177                //
178                tx.success();
179                return directives;
180            } catch (Exception e) {
181                logger.warning("ROLLBACK!");
182                throw new RuntimeException("Updating topic failed (" + model + ")", e);
183            } finally {
184                tx.finish();
185            }
186        }
187    
188        @Override
189        public Directives deleteTopic(long topicId) {
190            DeepaMehtaTransaction tx = beginTx();
191            try {
192                Directives directives = new Directives();
193                //
194                getTopic(topicId, false).delete(directives);    // fetchComposite=false
195                //
196                tx.success();
197                return directives;
198            } catch (Exception e) {
199                logger.warning("ROLLBACK!");
200                throw new RuntimeException("Deleting topic " + topicId + " failed", e);
201            } finally {
202                tx.finish();
203            }
204        }
205    
206    
207    
208        // === Associations ===
209    
210        @Override
211        public Association getAssociation(long assocId, boolean fetchComposite) {
212            logger.info("assocId=" + assocId + ", fetchComposite=" + fetchComposite);
213            try {
214                return instantiateAssociation(storageDecorator.fetchAssociation(assocId), fetchComposite);
215            } catch (Exception e) {
216                throw new RuntimeException("Fetching association " + assocId + " failed", e);
217            }
218        }
219    
220        @Override
221        public Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id,
222                                                               String roleTypeUri1, String roleTypeUri2,
223                                                               boolean fetchComposite) {
224            String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
225                ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\", fetchComposite=" +
226                fetchComposite;
227            try {
228                AssociationModel assoc = storageDecorator.fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1,
229                    roleTypeUri2);
230                return assoc != null ? instantiateAssociation(assoc, fetchComposite) : null;
231            } catch (Exception e) {
232                throw new RuntimeException("Fetching association failed (" + info + ")", e);
233            }
234        }
235    
236        @Override
237        public Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId,
238                                                                    String topicRoleTypeUri, String assocRoleTypeUri,
239                                                                    boolean fetchComposite) {
240            String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId +
241                ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri +
242                "\", fetchComposite=" + fetchComposite;
243            logger.info(info);
244            try {
245                AssociationModel assoc = storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri,
246                    topicId, assocId, topicRoleTypeUri, assocRoleTypeUri);
247                return assoc != null ? instantiateAssociation(assoc, fetchComposite) : null;
248            } catch (Exception e) {
249                throw new RuntimeException("Fetching association failed (" + info + ")", e);
250            }
251        }
252    
253        // ---
254    
255        @Override
256        public List<RelatedAssociation> getAssociations(String assocTypeUri) {
257            try {
258                return getAssociationType(assocTypeUri).getRelatedAssociations("dm4.core.instantiation",
259                    "dm4.core.type", "dm4.core.instance", assocTypeUri, false, false);
260                    // fetchComposite=false, fetchRelatingComposite=false
261            } catch (Exception e) {
262                throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")",
263                    e);
264            }
265        }
266    
267        @Override
268        public List<Association> getAssociations(long topic1Id, long topic2Id) {
269            return getAssociations(topic1Id, topic2Id, null);
270        }
271    
272        @Override
273        public List<Association> getAssociations(long topic1Id, long topic2Id, String assocTypeUri) {
274            logger.info("topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + ", assocTypeUri=\"" + assocTypeUri + "\"");
275            try {
276                // ### FIXME: fetchComposite=false, parameterize it
277                return instantiateAssociations(storageDecorator.fetchAssociations(assocTypeUri, topic1Id, topic2Id,
278                    null, null), false);     // roleTypeUri1=null, roleTypeUri2=null
279            } catch (Exception e) {
280                throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id +
281                    " failed (assocTypeUri=\"" + assocTypeUri + "\")", e);
282            }
283        }
284    
285        // ---
286    
287        @Override
288        public Iterable<Association> getAllAssociations() {
289            return new AssociationIterable(this);
290        }
291    
292        // ---
293    
294        @Override
295        public Association createAssociation(AssociationModel model, ClientState clientState) {
296            DeepaMehtaTransaction tx = beginTx();
297            try {
298                fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model, clientState);
299                //
300                Directives directives = new Directives();   // ### FIXME: directives are ignored
301                Association assoc = associationFactory(model, clientState, directives);
302                //
303                fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc, clientState, directives);
304                //
305                tx.success();
306                return assoc;
307            } catch (Exception e) {
308                logger.warning("ROLLBACK!");
309                throw new RuntimeException("Creating association failed (" + model + ")", e);
310            } finally {
311                tx.finish();
312            }
313        }
314    
315        @Override
316        public Directives updateAssociation(AssociationModel model, ClientState clientState) {
317            DeepaMehtaTransaction tx = beginTx();
318            try {
319                Directives directives = new Directives();
320                //
321                getAssociation(model.getId(), true).update(model, clientState, directives);     // fetchComposite=true
322                //
323                tx.success();
324                return directives;
325            } catch (Exception e) {
326                logger.warning("ROLLBACK!");
327                throw new RuntimeException("Updating association failed (" + model + ")", e);
328            } finally {
329                tx.finish();
330            }
331        }
332    
333        @Override
334        public Directives deleteAssociation(long assocId) {
335            DeepaMehtaTransaction tx = beginTx();
336            try {
337                Directives directives = new Directives();
338                //
339                getAssociation(assocId, false).delete(directives);  // fetchComposite=false
340                //
341                tx.success();
342                return directives;
343            } catch (Exception e) {
344                logger.warning("ROLLBACK!");
345                throw new RuntimeException("Deleting association " + assocId + " failed", e);
346            } finally {
347                tx.finish();
348            }
349        }
350    
351    
352    
353        // === Topic Types ===
354    
355        @Override
356        public List<String> getTopicTypeUris() {
357            try {
358                Topic metaType = instantiateTopic(storageDecorator.fetchTopic("uri",
359                    new SimpleValue("dm4.core.topic_type")), false);
360                ResultList<RelatedTopic> topicTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
361                    "dm4.core.instance", "dm4.core.topic_type", false, false, 0);
362                List<String> topicTypeUris = new ArrayList();
363                // add meta types
364                topicTypeUris.add("dm4.core.topic_type");
365                topicTypeUris.add("dm4.core.assoc_type");
366                topicTypeUris.add("dm4.core.meta_type");
367                topicTypeUris.add("dm4.core.meta_meta_type");
368                // add regular types
369                for (Topic topicType : topicTypes) {
370                    topicTypeUris.add(topicType.getUri());
371                }
372                return topicTypeUris;
373            } catch (Exception e) {
374                throw new RuntimeException("Fetching list of topic type URIs failed", e);
375            }
376        }
377    
378        @Override
379        public TopicType getTopicType(String uri) {
380            try {
381                return typeCache.getTopicType(uri);
382            } catch (Exception e) {
383                throw new RuntimeException("Fetching topic type \"" + uri + "\" failed", e);
384            }
385        }
386    
387        @Override
388        public List<TopicType> getAllTopicTypes() {
389            try {
390                List<TopicType> topicTypes = new ArrayList();
391                for (String uri : getTopicTypeUris()) {
392                    TopicType topicType = getTopicType(uri);
393                    topicTypes.add(topicType);
394                }
395                return topicTypes;
396            } catch (Exception e) {
397                throw new RuntimeException("Fetching all topic types failed", e);
398            }
399        }
400    
401        // ---
402    
403        @Override
404        public TopicType createTopicType(TopicTypeModel model, ClientState clientState) {
405            DeepaMehtaTransaction tx = beginTx();
406            try {
407                TopicType topicType = topicTypeFactory(model);
408                //
409                fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType, clientState);
410                //
411                tx.success();
412                return topicType;
413            } catch (Exception e) {
414                logger.warning("ROLLBACK!");
415                throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed (" + model + ")", e);
416            } finally {
417                tx.finish();
418            }
419        }
420    
421        @Override
422        public Directives updateTopicType(TopicTypeModel model, ClientState clientState) {
423            DeepaMehtaTransaction tx = beginTx();
424            try {
425                // Note: type lookup is by ID. The URI might have changed, the ID does not.
426                String topicTypeUri = getTopic(model.getId(), false).getUri();     // fetchComposite=false
427                TopicType topicType = getTopicType(topicTypeUri);
428                Directives directives = new Directives();
429                //
430                topicType.update(model, clientState, directives);
431                //
432                tx.success();
433                return directives;
434            } catch (Exception e) {
435                logger.warning("ROLLBACK!");
436                throw new RuntimeException("Updating topic type failed (" + model + ")", e);
437            } finally {
438                tx.finish();
439            }
440        }
441    
442        @Override
443        public Directives deleteTopicType(String topicTypeUri) {
444            DeepaMehtaTransaction tx = beginTx();
445            try {
446                TopicType topicType = getTopicType(topicTypeUri);
447                Directives directives = new Directives();
448                //
449                topicType.delete(directives);
450                //
451                tx.success();
452                return directives;
453            } catch (Exception e) {
454                logger.warning("ROLLBACK!");
455                throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e);
456            } finally {
457                tx.finish();
458            }
459        }
460    
461    
462    
463        // === Association Types ===
464    
465        @Override
466        public List<String> getAssociationTypeUris() {
467            try {
468                Topic metaType = instantiateTopic(storageDecorator.fetchTopic("uri",
469                    new SimpleValue("dm4.core.assoc_type")), false);
470                ResultList<RelatedTopic> assocTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
471                    "dm4.core.instance", "dm4.core.assoc_type", false, false, 0);
472                List<String> assocTypeUris = new ArrayList();
473                for (Topic assocType : assocTypes) {
474                    assocTypeUris.add(assocType.getUri());
475                }
476                return assocTypeUris;
477            } catch (Exception e) {
478                throw new RuntimeException("Fetching list of association type URIs failed", e);
479            }
480        }
481    
482        @Override
483        public AssociationType getAssociationType(String uri) {
484            try {
485                return typeCache.getAssociationType(uri);
486            } catch (Exception e) {
487                throw new RuntimeException("Fetching association type \"" + uri + "\" failed", e);
488            }
489        }
490    
491        @Override
492        public List<AssociationType> getAllAssociationTypes() {
493            try {
494                List<AssociationType> assocTypes = new ArrayList();
495                for (String uri : getAssociationTypeUris()) {
496                    AssociationType assocType = getAssociationType(uri);
497                    assocTypes.add(assocType);
498                }
499                return assocTypes;
500            } catch (Exception e) {
501                throw new RuntimeException("Fetching all association types failed", e);
502            }
503        }
504    
505        // ---
506    
507        @Override
508        public AssociationType createAssociationType(AssociationTypeModel model, ClientState clientState) {
509            DeepaMehtaTransaction tx = beginTx();
510            try {
511                AssociationType assocType = associationTypeFactory(model);
512                //
513                fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType, clientState);
514                //
515                tx.success();
516                return assocType;
517            } catch (Exception e) {
518                logger.warning("ROLLBACK!");
519                throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed (" + model + ")",
520                    e);
521            } finally {
522                tx.finish();
523            }
524        }
525    
526        @Override
527        public Directives updateAssociationType(AssociationTypeModel model, ClientState clientState) {
528            DeepaMehtaTransaction tx = beginTx();
529            try {
530                // Note: type lookup is by ID. The URI might have changed, the ID does not.
531                String assocTypeUri = getTopic(model.getId(), false).getUri();     // fetchComposite=false
532                AssociationType assocType = getAssociationType(assocTypeUri);
533                Directives directives = new Directives();
534                //
535                assocType.update(model, clientState, directives);
536                //
537                tx.success();
538                return directives;
539            } catch (Exception e) {
540                logger.warning("ROLLBACK!");
541                throw new RuntimeException("Updating association type failed (" + model + ")", e);
542            } finally {
543                tx.finish();
544            }
545        }
546    
547        @Override
548        public Directives deleteAssociationType(String assocTypeUri) {
549            DeepaMehtaTransaction tx = beginTx();
550            try {
551                AssociationType assocType = getAssociationType(assocTypeUri);
552                Directives directives = new Directives();
553                //
554                assocType.delete(directives);
555                //
556                tx.success();
557                return directives;
558            } catch (Exception e) {
559                logger.warning("ROLLBACK!");
560                throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e);
561            } finally {
562                tx.finish();
563            }
564        }
565    
566    
567    
568        // === Plugins ===
569    
570        @Override
571        public Plugin getPlugin(String pluginUri) {
572            return pluginManager.getPlugin(pluginUri);
573        }
574    
575        @Override
576        public List<PluginInfo> getPluginInfo() {
577            return pluginManager.getPluginInfo();
578        }
579    
580    
581    
582        // === Events ===
583    
584        @Override
585        public void fireEvent(DeepaMehtaEvent event, Object... params) {
586            eventManager.fireEvent(event, params);
587        }
588    
589        @Override
590        public void deliverEvent(String pluginUri, DeepaMehtaEvent event, Object... params) {
591            eventManager.deliverEvent(pluginUri, event, params);
592        }
593    
594    
595    
596        // === Properties ===
597    
598        @Override
599        public List<Topic> getTopicsByProperty(String propUri, Object propValue) {
600            return instantiateTopics(storageDecorator.fetchTopicsByProperty(propUri, propValue), false);
601                // fetchComposite=false
602        }
603    
604        @Override
605        public List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) {
606            return instantiateTopics(storageDecorator.fetchTopicsByPropertyRange(propUri, from, to), false);
607                // fetchComposite=false
608        }
609    
610        @Override
611        public List<Association> getAssociationsByProperty(String propUri, Object propValue) {
612            return instantiateAssociations(storageDecorator.fetchAssociationsByProperty(propUri, propValue), false);
613                // fetchComposite=false
614        }
615    
616        @Override
617        public List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) {
618            return instantiateAssociations(storageDecorator.fetchAssociationsByPropertyRange(propUri, from, to), false);
619                // fetchComposite=false
620        }
621    
622    
623    
624        // === Misc ===
625    
626        @Override
627        public DeepaMehtaTransaction beginTx() {
628            return storageDecorator.beginTx();
629        }
630    
631        @Override
632        public TypeStorage getTypeStorage() {
633            return typeStorage;
634        }
635    
636        @Override
637        public Object getDatabaseVendorObject() {
638            return storageDecorator.getDatabaseVendorObject();
639        }
640    
641    
642    
643        // ----------------------------------------------------------------------------------------- Package Private Methods
644    
645    
646    
647        // === Helper ===
648    
649        void createTopicInstantiation(long topicId, String topicTypeUri) {
650            try {
651                AssociationModel assoc = new AssociationModel("dm4.core.instantiation",
652                    new TopicRoleModel(topicTypeUri, "dm4.core.type"),
653                    new TopicRoleModel(topicId, "dm4.core.instance"));
654                storageDecorator.storeAssociation(assoc);   // direct storage calls used here ### explain
655                storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
656                createAssociationInstantiation(assoc.getId(), assoc.getTypeUri());
657            } catch (Exception e) {
658                throw new RuntimeException("Associating topic " + topicId +
659                    " with topic type \"" + topicTypeUri + "\" failed", e);
660            }
661        }
662    
663        void createAssociationInstantiation(long assocId, String assocTypeUri) {
664            try {
665                AssociationModel assoc = new AssociationModel("dm4.core.instantiation",
666                    new TopicRoleModel(assocTypeUri, "dm4.core.type"),
667                    new AssociationRoleModel(assocId, "dm4.core.instance"));
668                storageDecorator.storeAssociation(assoc);   // direct storage calls used here ### explain
669                storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
670            } catch (Exception e) {
671                throw new RuntimeException("Associating association " + assocId +
672                    " with association type \"" + assocTypeUri + "\" failed", e);
673            }
674        }
675    
676        // ---
677    
678        /**
679         * Convenience method. ### to be dropped?
680         */
681        Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
682            return createAssociation(typeUri, roleModel1, roleModel2, null);    // ### FIXME: clientState=null
683        }
684    
685        /**
686         * Convenience method. ### to be dropped?
687         */
688        Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2, ClientState clientState) {
689            return createAssociation(new AssociationModel(typeUri, roleModel1, roleModel2), clientState);
690        }
691    
692    
693    
694        // ------------------------------------------------------------------------------------------------- Private Methods
695    
696        /**
697         * Attaches this core service to a topic model fetched from storage layer.
698         * Optionally fetches the topic's composite value from storage layer.
699         */
700        Topic instantiateTopic(TopicModel model, boolean fetchComposite) {
701            fetchCompositeValue(model, fetchComposite);
702            return new AttachedTopic(model, this);
703        }
704    
705        private List<Topic> instantiateTopics(List<TopicModel> models, boolean fetchComposite) {
706            List<Topic> topics = new ArrayList();
707            for (TopicModel model : models) {
708                topics.add(instantiateTopic(model, fetchComposite));
709            }
710            return topics;
711        }
712    
713        // ---
714    
715        RelatedTopic instantiateRelatedTopic(RelatedTopicModel model, boolean fetchComposite,
716                                                                      boolean fetchRelatingComposite) {
717            fetchCompositeValue(model, fetchComposite, fetchRelatingComposite);
718            return new AttachedRelatedTopic(model, this);
719        }
720    
721        ResultList<RelatedTopic> instantiateRelatedTopics(ResultList<RelatedTopicModel> models,
722                                                          boolean fetchComposite, boolean fetchRelatingComposite) {
723            List<RelatedTopic> relTopics = new ArrayList();
724            for (RelatedTopicModel model : models) {
725                relTopics.add(instantiateRelatedTopic(model, fetchComposite, fetchRelatingComposite));
726            }
727            return new ResultList<RelatedTopic>(models.getTotalCount(), relTopics);
728        }
729    
730        // ===
731    
732        /**
733         * Attaches this core service to an association fetched from storage layer.
734         * Optionally fetches the topic's composite value from storage layer.
735         */
736        Association instantiateAssociation(AssociationModel model, boolean fetchComposite) {
737            fetchCompositeValue(model, fetchComposite);
738            return new AttachedAssociation(model, this);
739        }
740    
741        List<Association> instantiateAssociations(List<AssociationModel> models, boolean fetchComposite) {
742            List<Association> assocs = new ArrayList();
743            for (AssociationModel model : models) {
744                assocs.add(instantiateAssociation(model, fetchComposite));
745            }
746            return assocs;
747        }
748    
749        // ---
750    
751        RelatedAssociation instantiateRelatedAssociation(RelatedAssociationModel model, boolean fetchComposite,
752                                                                                        boolean fetchRelatingComposite) {
753            if (fetchComposite || fetchRelatingComposite) {
754                // ### TODO
755                throw new RuntimeException("not yet implemented");
756            }
757            return new AttachedRelatedAssociation(model, this);
758        }
759    
760        List<RelatedAssociation> instantiateRelatedAssociations(Iterable<RelatedAssociationModel> models,
761                                                                boolean fetchComposite, boolean fetchRelatingComposite) {
762            List<RelatedAssociation> relAssocs = new ArrayList();
763            for (RelatedAssociationModel model : models) {
764                relAssocs.add(instantiateRelatedAssociation(model, fetchComposite, fetchRelatingComposite));
765            }
766            return relAssocs;
767        }
768    
769        // ===
770    
771        private void fetchCompositeValue(DeepaMehtaObjectModel model, boolean fetchComposite) {
772            if (fetchComposite) {
773                valueStorage.fetchCompositeValue(model);
774            }
775        }
776    
777        private void fetchCompositeValue(RelatedTopicModel model, boolean fetchComposite, boolean fetchRelatingComposite) {
778            fetchCompositeValue(model, fetchComposite);
779            if (fetchRelatingComposite) {
780                valueStorage.fetchCompositeValue(model.getRelatingAssociation());
781            }
782        }
783    
784        // ---
785    
786        /**
787         * Factory method: creates a new topic in the DB according to the given topic model and returns a topic instance.
788         */
789        private Topic topicFactory(TopicModel model, ClientState clientState, Directives directives) {
790            // 1) store in DB
791            storageDecorator.storeTopic(model);
792            valueStorage.storeValue(model, clientState, directives);
793            createTopicInstantiation(model.getId(), model.getTypeUri());
794            //
795            // 2) create application object
796            return new AttachedTopic(model, this);
797        }
798    
799        /**
800         * Factory method: creates a new association in the DB according to the given association model and returns an
801         * association instance.
802         */
803        private Association associationFactory(AssociationModel model, ClientState clientState, Directives directives) {
804            // 1) store in DB
805            storageDecorator.storeAssociation(model);
806            valueStorage.storeValue(model, clientState, directives);
807            createAssociationInstantiation(model.getId(), model.getTypeUri());
808            //
809            // 2) create application object
810            return new AttachedAssociation(model, this);
811        }
812    
813        // ---
814    
815        /**
816         * Factory method: creates a new topic type in the DB according to the given topic type model
817         * and returns a topic type instance.
818         */
819        private TopicType topicTypeFactory(TopicTypeModel model) {
820            // 1) store in DB
821            createTypeTopic(model, DEFAULT_TOPIC_TYPE_URI);         // store generic topic
822            typeStorage.storeType(model);                           // store type-specific parts
823            //
824            // 2) create application object
825            TopicType topicType = new AttachedTopicType(model, this);
826            typeCache.putTopicType(topicType);
827            //
828            return topicType;
829        }
830    
831        /**
832         * Factory method: creates a new association type in the DB according to the given association type model
833         * and returns a topic type instance.
834         */
835        private AssociationType associationTypeFactory(AssociationTypeModel model) {
836            // 1) store in DB
837            createTypeTopic(model, DEFAULT_ASSOCIATION_TYPE_URI);   // store generic topic
838            typeStorage.storeType(model);                           // store type-specific parts
839            //
840            // 2) create application object
841            AssociationType assocType = new AttachedAssociationType(model, this);
842            typeCache.putAssociationType(assocType);
843            //
844            return assocType;
845        }
846    
847        // ---
848    
849        private void createTypeTopic(TopicModel model, String defaultUriPrefix) {
850            Topic typeTopic = topicFactory(model, null, null);   // ### FIXME: clientState, directives
851            // If no URI is set the type gets a default URI based on its ID.
852            // Note: this must be done *after* the topic is created. The ID is not known before.
853            if (typeTopic.getUri().equals("")) {
854                typeTopic.setUri(defaultUriPrefix + typeTopic.getId());
855            }
856        }
857    
858    
859    
860        // === Bootstrap ===
861    
862        /**
863         * Setups the database:
864         *   1) initializes the database.
865         *   2) in case of a clean install: sets up the bootstrap content.
866         *   3) runs the core migrations.
867         * <p>
868         * Called from {@link CoreActivator#start}.
869         */
870        private void setupDB() {
871            DeepaMehtaTransaction tx = beginTx();
872            try {
873                logger.info("----- Setting up the database -----");
874                boolean isCleanInstall = storageDecorator.init();
875                if (isCleanInstall) {
876                    setupBootstrapContent();
877                }
878                migrationManager.runCoreMigrations(isCleanInstall);
879                tx.success();
880                tx.finish();
881                logger.info("----- Setting up the database complete -----");
882            } catch (Exception e) {
883                logger.warning("ROLLBACK!");
884                // Note: we don't put finish() in a finally clause here because
885                // in case of error the database has to be shut down.
886                tx.finish();
887                storageDecorator.shutdown();
888                throw new RuntimeException("Setting up the database failed", e);
889            }
890        }
891    
892        private void setupBootstrapContent() {
893            try {
894                // Create meta types "Topic Type" and "Association Type" -- needed to create topic types and
895                // asscociation types
896                TopicModel t = new TopicModel("dm4.core.topic_type", "dm4.core.meta_type",
897                    new SimpleValue("Topic Type"));
898                TopicModel a = new TopicModel("dm4.core.assoc_type", "dm4.core.meta_type",
899                    new SimpleValue("Association Type"));
900                _createTopic(t);
901                _createTopic(a);
902                // Create topic types "Data Type" and "Role Type"
903                // ### Note: the topic type "Data Type" depends on the data type "Text" and the data type "Text" in turn
904                // depends on the topic type "Data Type". To resolve this circle we use a low-level (storage) call here
905                // and postpone the data type association.
906                TopicModel dataType = new TopicTypeModel("dm4.core.data_type", "Data Type", "dm4.core.text");
907                TopicModel roleType = new TopicTypeModel("dm4.core.role_type", "Role Type", "dm4.core.text");
908                _createTopic(dataType);
909                _createTopic(roleType);
910                // Create data type "Text"
911                TopicModel text = new TopicModel("dm4.core.text", "dm4.core.data_type", new SimpleValue("Text"));
912                _createTopic(text);
913                // Create role types "Default", "Type", and "Instance"
914                TopicModel deflt = new TopicModel("dm4.core.default",  "dm4.core.role_type", new SimpleValue("Default"));
915                TopicModel type  = new TopicModel("dm4.core.type",     "dm4.core.role_type", new SimpleValue("Type"));
916                TopicModel inst  = new TopicModel("dm4.core.instance", "dm4.core.role_type", new SimpleValue("Instance"));
917                _createTopic(deflt);
918                _createTopic(type);
919                _createTopic(inst);
920                // Create association type "Aggregation" -- needed to associate topic/association types with data types
921                TopicModel aggregation = new AssociationTypeModel("dm4.core.aggregation", "Aggregation", "dm4.core.text");
922                _createTopic(aggregation);
923                // Create association type "Instantiation" -- needed to associate topics with topic types
924                TopicModel instn = new AssociationTypeModel("dm4.core.instantiation", "Instantiation", "dm4.core.text");
925                _createTopic(instn);
926                //
927                // 1) Postponed topic type association
928                //
929                // Note: createTopicInstantiation() creates the associations by *low-level* (storage) calls.
930                // That's why the associations can be created *before* their type (here: "dm4.core.instantiation")
931                // is fully constructed (the type's data type is not yet associated => step 2).
932                createTopicInstantiation(t.getId(), t.getTypeUri());
933                createTopicInstantiation(a.getId(), a.getTypeUri());
934                createTopicInstantiation(dataType.getId(), dataType.getTypeUri());
935                createTopicInstantiation(roleType.getId(), roleType.getTypeUri());
936                createTopicInstantiation(text.getId(), text.getTypeUri());
937                createTopicInstantiation(deflt.getId(), deflt.getTypeUri());
938                createTopicInstantiation(type.getId(), type.getTypeUri());
939                createTopicInstantiation(inst.getId(), inst.getTypeUri());
940                createTopicInstantiation(aggregation.getId(), aggregation.getTypeUri());
941                createTopicInstantiation(instn.getId(), instn.getTypeUri());
942                //
943                // 2) Postponed data type association
944                //
945                // Note: associateDataType() creates the association by a *high-level* (service) call.
946                // This requires the association type (here: dm4.core.aggregation) to be fully constructed already.
947                // That's why the topic type associations (step 1) must be performed *before* the data type associations.
948                // ### FIXDOC: not true anymore
949                //
950                // Note: at time of the first associateDataType() call the required association type (dm4.core.aggregation)
951                // is *not* fully constructed yet! (it gets constructed through this very call). This works anyway because
952                // the data type assigning association is created *before* the association type is fetched.
953                // (see AttachedAssociation.store(): storage.storeAssociation() is called before getType()
954                // in AttachedDeepaMehtaObject.store().)
955                // ### FIXDOC: not true anymore
956                //
957                // Important is that associateDataType("dm4.core.aggregation") is the first call here.
958                // ### FIXDOC: not true anymore
959                //
960                // Note: _associateDataType() creates the data type assigning association by a *low-level* (storage) call.
961                // A high-level (service) call would fail while setting the association's value. The involved getType()
962                // would fail (not because the association is missed -- it's created meanwhile, but)
963                // because this involves fetching the association including its value. The value doesn't exist yet,
964                // because its setting forms the begin of this vicious circle.
965                _associateDataType("dm4.core.meta_type",  "dm4.core.text");
966                _associateDataType("dm4.core.topic_type", "dm4.core.text");
967                _associateDataType("dm4.core.assoc_type", "dm4.core.text");
968                _associateDataType("dm4.core.data_type",  "dm4.core.text");
969                _associateDataType("dm4.core.role_type",  "dm4.core.text");
970                //
971                _associateDataType("dm4.core.aggregation",   "dm4.core.text");
972                _associateDataType("dm4.core.instantiation", "dm4.core.text");
973            } catch (Exception e) {
974                throw new RuntimeException("Setting up the bootstrap content failed", e);
975            }
976        }
977    
978        // ---
979    
980        /**
981         * Low-level method that stores a topic without its "Instantiation" association.
982         * Needed for bootstrapping.
983         */
984        private void _createTopic(TopicModel model) {
985            storageDecorator.storeTopic(model);
986            storageDecorator.storeTopicValue(model.getId(), model.getSimpleValue());
987        }
988    
989        /**
990         * Low-level method that stores an (data type) association without its "Instantiation" association.
991         * Needed for bootstrapping.
992         */
993        private void _associateDataType(String typeUri, String dataTypeUri) {
994            AssociationModel assoc = new AssociationModel("dm4.core.aggregation",
995                new TopicRoleModel(typeUri,     "dm4.core.type"),
996                new TopicRoleModel(dataTypeUri, "dm4.core.default"));
997            storageDecorator.storeAssociation(assoc);
998            storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
999        }
1000    
1001        // ---
1002    
1003        private void bootstrapTypeCache() {
1004            typeCache.putTopicType(new AttachedTopicType(new TopicTypeModel("dm4.core.meta_meta_type",
1005                "dm4.core.meta_meta_meta_type", "Meta Meta Type", "dm4.core.text"), this));
1006        }
1007    }