001    package de.deepamehta.plugins.facets;
002    
003    import de.deepamehta.plugins.facets.model.FacetValue;
004    import de.deepamehta.plugins.facets.service.FacetsService;
005    
006    import de.deepamehta.core.Association;
007    import de.deepamehta.core.AssociationDefinition;
008    import de.deepamehta.core.DeepaMehtaObject;
009    import de.deepamehta.core.RelatedTopic;
010    import de.deepamehta.core.Topic;
011    import de.deepamehta.core.model.AssociationModel;
012    import de.deepamehta.core.model.CompositeValueModel;
013    import de.deepamehta.core.model.TopicModel;
014    import de.deepamehta.core.model.TopicRoleModel;
015    import de.deepamehta.core.osgi.PluginActivator;
016    import de.deepamehta.core.service.ClientState;
017    import de.deepamehta.core.service.Directives;
018    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
019    import de.deepamehta.core.util.DeepaMehtaUtils;
020    
021    import javax.ws.rs.GET;
022    import javax.ws.rs.POST;
023    import javax.ws.rs.PUT;
024    import javax.ws.rs.HeaderParam;
025    import javax.ws.rs.Path;
026    import javax.ws.rs.PathParam;
027    import javax.ws.rs.QueryParam;
028    import javax.ws.rs.Produces;
029    import javax.ws.rs.Consumes;
030    
031    import java.util.List;
032    import java.util.logging.Logger;
033    
034    
035    
036    @Path("/facet")
037    @Consumes("application/json")
038    @Produces("application/json")
039    public class FacetsPlugin extends PluginActivator implements FacetsService {
040    
041        // ---------------------------------------------------------------------------------------------- Instance Variables
042    
043        private Logger logger = Logger.getLogger(getClass().getName());
044    
045        // -------------------------------------------------------------------------------------------------- Public Methods
046    
047    
048    
049        // ************************************
050        // *** FacetsService Implementation ***
051        // ************************************
052    
053    
054    
055        @GET
056        @Path("/{facet_type_uri}/topic/{id}")
057        @Override
058        public Topic getFacet(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri) {
059            return getFacet(dms.getTopic(topicId, false), facetTypeUri);        // fetchComposite=false
060        }
061    
062        @Override
063        public Topic getFacet(DeepaMehtaObject object, String facetTypeUri) {
064            // ### TODO: integrity check: is the object an instance of that facet type?
065            return fetchChildTopic(object, getAssocDef(facetTypeUri), true);    // fetchComposite=true
066        }
067    
068        // ---
069    
070        @GET
071        @Path("/multi/{facet_type_uri}/topic/{id}")
072        @Override
073        public List<RelatedTopic> getFacets(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri)
074                                                                                                                    {
075            return getFacets(dms.getTopic(topicId, false), facetTypeUri);       // fetchComposite=false
076        }
077    
078        @Override
079        public List<RelatedTopic> getFacets(DeepaMehtaObject object, String facetTypeUri) {
080            // ### TODO: integrity check: is the object an instance of that facet type?
081            return fetchChildTopics(object, getAssocDef(facetTypeUri), true);   // fetchComposite=true
082        }
083    
084        // ---
085    
086        @GET
087        @Path("/topic/{id}")
088        @Override
089        public Topic getFacettedTopic(@PathParam("id") long topicId,
090                                      @QueryParam("facet_type_uri") List<String> facetTypeUris) {
091            Topic topic = dms.getTopic(topicId, true);
092            CompositeValueModel comp = topic.getCompositeValue().getModel();
093            for (String facetTypeUri : facetTypeUris) {
094                String childTypeUri = getChildTypeUri(facetTypeUri);
095                if (!isMultiFacet(facetTypeUri)) {
096                    Topic value = getFacet(topic, facetTypeUri);
097                    if (value != null) {
098                        comp.put(childTypeUri, value.getModel());
099                    }
100                } else {
101                    List<RelatedTopic> values = getFacets(topic, facetTypeUri);
102                    comp.put(childTypeUri, DeepaMehtaUtils.toTopicModels(values));
103                }
104            }
105            return topic;
106        }
107    
108        @POST
109        @Path("/{facet_type_uri}/topic/{id}")
110        @Override
111        public void addFacetTypeToTopic(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri) {
112            dms.createAssociation(new AssociationModel("dm4.core.instantiation",
113                new TopicRoleModel(topicId,      "dm4.core.instance"),
114                new TopicRoleModel(facetTypeUri, "dm4.facets.facet")), null);   // clientState=null
115        }
116    
117        // ---
118    
119        @PUT
120        @Path("/{facet_type_uri}/topic/{id}")
121        @Override
122        public void updateFacet(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri,
123                                                 FacetValue value, @HeaderParam("Cookie") ClientState clientState) {
124            DeepaMehtaTransaction tx = dms.beginTx();
125            try {
126                updateFacet(dms.getTopic(topicId, false), facetTypeUri, value, clientState, new Directives());
127                //
128                tx.success();
129            } catch (Exception e) {
130                logger.warning("ROLLBACK!");
131                throw new RuntimeException("Updating facet \"" + facetTypeUri + "\" of topic " + topicId +
132                    " failed (value=" + value + ")", e);
133            } finally {
134                tx.finish();
135            }
136        }
137    
138        @Override
139        public void updateFacet(DeepaMehtaObject object, String facetTypeUri, FacetValue value,
140                                                         ClientState clientState, Directives directives) {
141            AssociationDefinition assocDef = getAssocDef(facetTypeUri);
142            if (!isMultiFacet(facetTypeUri)) {
143                object.updateChildTopic(value.getTopic(), assocDef, clientState, directives);
144            } else {
145                object.updateChildTopics(value.getTopics(), assocDef, clientState, directives);
146            }
147        }
148    
149        // ---
150    
151        // Note: there is a similar private method in AttachedDeepaMehtaObject:
152        // fetchChildTopic(AssociationDefinition assocDef, long childTopicId, boolean fetchComposite)
153        // ### TODO: Extend DeepaMehtaObject interface by hasChildTopic()?
154        @Override
155        public boolean hasFacet(long topicId, String facetTypeUri, long facetTopicId) {
156            String assocTypeUri = getAssocDef(facetTypeUri).getInstanceLevelAssocTypeUri();
157            Association assoc = dms.getAssociation(assocTypeUri, topicId, facetTopicId, "dm4.core.parent", "dm4.core.child",
158                false);     // fetchComposite=false
159            return assoc != null;
160        }
161    
162    
163    
164        // ------------------------------------------------------------------------------------------------- Private Methods
165    
166        /**
167         * Fetches and returns a child topic or <code>null</code> if no such topic extists.
168         * <p>
169         * Note: There is a principal copy in AttachedDeepaMehtaObject but here the precondition is different:
170         * The given association definition must not necessarily originate from the given object's type definition.
171         * ### TODO: meanwhile we have the ValueStorage. Can we use its method instead?
172         */
173        private RelatedTopic fetchChildTopic(DeepaMehtaObject object, AssociationDefinition assocDef,
174                                                                      boolean fetchComposite) {
175            String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
176            String othersTypeUri = assocDef.getChildTypeUri();
177            return object.getRelatedTopic(assocTypeUri, "dm4.core.parent", "dm4.core.child", othersTypeUri, fetchComposite,
178                false);
179        }
180    
181        /**
182         * Fetches and returns child topics.
183         * <p>
184         * Note: There is a principal copy in AttachedDeepaMehtaObject but here the precondition is different:
185         * The given association definition must not necessarily originate from the given object's type definition.
186         * ### TODO: meanwhile we have the ValueStorage. Can we use its method instead?
187         */
188        private List<RelatedTopic> fetchChildTopics(DeepaMehtaObject object, AssociationDefinition assocDef,
189                                                                             boolean fetchComposite) {
190            String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
191            String othersTypeUri = assocDef.getChildTypeUri();
192            return object.getRelatedTopics(assocTypeUri, "dm4.core.parent", "dm4.core.child", othersTypeUri, fetchComposite,
193                false, 0).getItems();
194        }
195    
196        // ---
197    
198        private boolean isMultiFacet(String facetTypeUri) {
199            return getAssocDef(facetTypeUri).getChildCardinalityUri().equals("dm4.core.many");
200        }
201    
202        private String getChildTypeUri(String facetTypeUri) {
203            return getAssocDef(facetTypeUri).getChildTypeUri();
204        }
205    
206        private AssociationDefinition getAssocDef(String facetTypeUri) {
207            // Note: a facet type has exactly *one* association definition
208            return dms.getTopicType(facetTypeUri).getAssocDefs().iterator().next();
209        }
210    }