001package de.deepamehta.facets;
002
003import de.deepamehta.core.Association;
004import de.deepamehta.core.AssociationDefinition;
005import de.deepamehta.core.DeepaMehtaObject;
006import de.deepamehta.core.RelatedTopic;
007import de.deepamehta.core.Topic;
008import de.deepamehta.core.model.ChildTopicsModel;
009import de.deepamehta.core.model.RelatedTopicModel;
010import de.deepamehta.core.model.facets.FacetValueModel;
011import de.deepamehta.core.osgi.PluginActivator;
012import de.deepamehta.core.service.Transactional;
013import de.deepamehta.core.util.DeepaMehtaUtils;
014
015import org.codehaus.jettison.json.JSONObject;
016
017import javax.ws.rs.GET;
018import javax.ws.rs.POST;
019import javax.ws.rs.PUT;
020import javax.ws.rs.HeaderParam;
021import javax.ws.rs.Path;
022import javax.ws.rs.PathParam;
023import javax.ws.rs.QueryParam;
024import javax.ws.rs.Produces;
025import javax.ws.rs.Consumes;
026
027import java.util.List;
028import java.util.logging.Logger;
029
030
031
032// ### TODO: support custom assoc types also for facets.
033// Some assocDef.getChildTypeUri() calls must be replaced by assocDef.getAssocDefUri().
034@Path("/facet")
035@Consumes("application/json")
036@Produces("application/json")
037public class FacetsPlugin extends PluginActivator implements FacetsService {
038
039    // ---------------------------------------------------------------------------------------------- Instance Variables
040
041    private Logger logger = Logger.getLogger(getClass().getName());
042
043    // -------------------------------------------------------------------------------------------------- Public Methods
044
045
046
047    // ************************************
048    // *** FacetsService Implementation ***
049    // ************************************
050
051
052
053    @GET
054    @Path("/{facet_type_uri}/topic/{id}")
055    @Override
056    public RelatedTopic getFacet(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri) {
057        return getFacet(dm4.getTopic(topicId), facetTypeUri);
058    }
059
060    @Override
061    public RelatedTopic getFacet(DeepaMehtaObject object, String facetTypeUri) {
062        // ### TODO: integrity check: is the object an instance of that facet type?
063        return fetchChildTopic(object, getAssocDef(facetTypeUri));
064    }
065
066    // ---
067
068    @GET
069    @Path("/multi/{facet_type_uri}/topic/{id}")
070    @Override
071    public List<RelatedTopic> getFacets(@PathParam("id") long topicId,
072                                        @PathParam("facet_type_uri") String facetTypeUri) {
073        return getFacets(dm4.getTopic(topicId), facetTypeUri);
074    }
075
076    @Override
077    public List<RelatedTopic> getFacets(DeepaMehtaObject object, String facetTypeUri) {
078        // ### TODO: integrity check: is the object an instance of that facet type?
079        return fetchChildTopics(object, getAssocDef(facetTypeUri));
080    }
081
082    // ---
083
084    @GET
085    @Path("/topic/{id}")
086    @Override
087    public Topic getFacettedTopic(@PathParam("id") long topicId,
088                                  @QueryParam("facet_type_uri") List<String> facetTypeUris) {
089        try {
090            Topic topic = dm4.getTopic(topicId);
091            ChildTopicsModel childTopics = topic.getChildTopics().getModel();
092            for (String facetTypeUri : facetTypeUris) {
093                String childTypeUri = getChildTypeUri(facetTypeUri);
094                if (!isMultiFacet(facetTypeUri)) {
095                    Topic value = getFacet(topic, facetTypeUri);
096                    if (value != null) {
097                        childTopics.put(childTypeUri, value.getModel());
098                    }
099                } else {
100                    List<RelatedTopic> values = getFacets(topic, facetTypeUri);
101                    // Note: without the type witness the generic put() method (which takes an Object) would be called
102                    childTopics.put(childTypeUri, DeepaMehtaUtils.<RelatedTopicModel>toModelList(values));
103                }
104            }
105            return topic;
106        } catch (Exception e) {
107            throw new RuntimeException("Getting facetted topic " + topicId + " failed (facetTypeUris=" + facetTypeUris +
108                ")", e);
109        }
110    }
111
112    @POST
113    @Path("/{facet_type_uri}/topic/{id}")
114    @Transactional
115    @Override
116    public void addFacetTypeToTopic(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri) {
117        dm4.createAssociation(mf.newAssociationModel("dm4.core.instantiation",
118            mf.newTopicRoleModel(topicId,      "dm4.core.instance"),
119            mf.newTopicRoleModel(facetTypeUri, "dm4.facets.facet")
120        ));
121    }
122
123    // ---
124
125    @PUT
126    @Path("/{facet_type_uri}/topic/{id}")
127    @Transactional
128    @Override
129    public void updateFacet(@PathParam("id") long topicId, @PathParam("facet_type_uri") String facetTypeUri,
130                                                                                        FacetValueModel value) {
131        try {
132            updateFacet(dm4.getTopic(topicId), facetTypeUri, value);
133        } catch (Exception e) {
134            throw new RuntimeException("Updating facet \"" + facetTypeUri + "\" of topic " + topicId +
135                " failed (value=" + value + ")", e);
136        }
137    }
138
139    @Override
140    public void updateFacet(DeepaMehtaObject object, String facetTypeUri, FacetValueModel value) {
141        AssociationDefinition assocDef = getAssocDef(facetTypeUri);
142        if (!isMultiFacet(facetTypeUri)) {
143            object.updateChildTopic(value.getTopic(), assocDef);
144        } else {
145            object.updateChildTopics(value.getTopics(), assocDef);
146        }
147    }
148
149    // ---
150
151    // Note: there is a similar private method in DeepaMehtaObjectImpl:
152    // fetchChildTopic(AssociationDefinition assocDef, long childTopicId)
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 = dm4.getAssociation(assocTypeUri, topicId, facetTopicId,
158            "dm4.core.parent", "dm4.core.child");
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 DeepaMehtaObjectImpl 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        String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
175        String othersTypeUri = assocDef.getChildTypeUri();
176        return object.getRelatedTopic(assocTypeUri, "dm4.core.parent", "dm4.core.child", othersTypeUri);
177    }
178
179    /**
180     * Fetches and returns child topics.
181     * <p>
182     * Note: There is a principal copy in DeepaMehtaObjectImpl but here the precondition is different:
183     * The given association definition must not necessarily originate from the given object's type definition.
184     * ### TODO: meanwhile we have the ValueStorage. Can we use its method instead?
185     */
186    private List<RelatedTopic> fetchChildTopics(DeepaMehtaObject object, AssociationDefinition assocDef) {
187        String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
188        String othersTypeUri = assocDef.getChildTypeUri();
189        return object.getRelatedTopics(assocTypeUri, "dm4.core.parent", "dm4.core.child", othersTypeUri);
190    }
191
192    // ---
193
194    private boolean isMultiFacet(String facetTypeUri) {
195        return getAssocDef(facetTypeUri).getChildCardinalityUri().equals("dm4.core.many");
196    }
197
198    private String getChildTypeUri(String facetTypeUri) {
199        return getAssocDef(facetTypeUri).getChildTypeUri();
200    }
201
202    private AssociationDefinition getAssocDef(String facetTypeUri) {
203        // Note: a facet type has exactly *one* association definition
204        return dm4.getTopicType(facetTypeUri).getAssocDefs().iterator().next();
205    }
206}