001package systems.dmx.facets;
002
003import systems.dmx.core.Association;
004import systems.dmx.core.AssociationDefinition;
005import systems.dmx.core.DMXObject;
006import systems.dmx.core.RelatedTopic;
007import systems.dmx.core.Topic;
008import systems.dmx.core.model.ChildTopicsModel;
009import systems.dmx.core.model.RelatedTopicModel;
010import systems.dmx.core.model.facets.FacetValueModel;
011import systems.dmx.core.osgi.PluginActivator;
012import systems.dmx.core.service.Transactional;
013import systems.dmx.core.util.DMXUtils;
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(dmx.getTopic(topicId), facetTypeUri);
058    }
059
060    @Override
061    public RelatedTopic getFacet(DMXObject 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(dmx.getTopic(topicId), facetTypeUri);
074    }
075
076    @Override
077    public List<RelatedTopic> getFacets(DMXObject 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 = dmx.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, DMXUtils.<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        dmx.createAssociation(mf.newAssociationModel("dmx.core.instantiation",
118            mf.newTopicRoleModel(topicId,      "dmx.core.instance"),
119            mf.newTopicRoleModel(facetTypeUri, "dmx.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(dmx.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(DMXObject object, String facetTypeUri, FacetValueModel value) {
141        object.updateChildTopics(value, getAssocDef(facetTypeUri));
142    }
143
144    // ---
145
146    // Note: there is a similar private method in DMXObjectImpl:
147    // fetchChildTopic(AssociationDefinition assocDef, long childTopicId)
148    // ### TODO: Extend DMXObject interface by hasChildTopic()?
149    @Override
150    public boolean hasFacet(long topicId, String facetTypeUri, long facetTopicId) {
151        String assocTypeUri = getAssocDef(facetTypeUri).getInstanceLevelAssocTypeUri();
152        Association assoc = dmx.getAssociation(assocTypeUri, topicId, facetTopicId,
153            "dmx.core.parent", "dmx.core.child");
154        return assoc != null;
155    }
156
157
158
159    // ------------------------------------------------------------------------------------------------- Private Methods
160
161    /**
162     * Fetches and returns a child topic or <code>null</code> if no such topic extists.
163     * <p>
164     * Note: There is a principal copy in DMXObjectImpl but here the precondition is different:
165     * The given association definition must not necessarily originate from the given object's type definition.
166     * ### TODO: meanwhile we have the ValueStorage. Can we use its method instead?
167     */
168    private RelatedTopic fetchChildTopic(DMXObject object, AssociationDefinition assocDef) {
169        String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
170        String othersTypeUri = assocDef.getChildTypeUri();
171        return object.getRelatedTopic(assocTypeUri, "dmx.core.parent", "dmx.core.child", othersTypeUri);
172    }
173
174    /**
175     * Fetches and returns child topics.
176     * <p>
177     * Note: There is a principal copy in DMXObjectImpl but here the precondition is different:
178     * The given association definition must not necessarily originate from the given object's type definition.
179     * ### TODO: meanwhile we have the ValueStorage. Can we use its method instead?
180     */
181    private List<RelatedTopic> fetchChildTopics(DMXObject object, AssociationDefinition assocDef) {
182        String assocTypeUri  = assocDef.getInstanceLevelAssocTypeUri();
183        String othersTypeUri = assocDef.getChildTypeUri();
184        return object.getRelatedTopics(assocTypeUri, "dmx.core.parent", "dmx.core.child", othersTypeUri);
185    }
186
187    // ---
188
189    private boolean isMultiFacet(String facetTypeUri) {
190        return getAssocDef(facetTypeUri).getChildCardinalityUri().equals("dmx.core.many");
191    }
192
193    private String getChildTypeUri(String facetTypeUri) {
194        return getAssocDef(facetTypeUri).getChildTypeUri();
195    }
196
197    private AssociationDefinition getAssocDef(String facetTypeUri) {
198        // Note: a facet type has exactly *one* association definition
199        return dmx.getTopicType(facetTypeUri).getAssocDefs().iterator().next();
200    }
201}