001package systems.dmx.webservice;
002
003import systems.dmx.core.Association;
004import systems.dmx.core.AssociationType;
005import systems.dmx.core.DMXObject;
006import systems.dmx.core.JSONEnabled;
007import systems.dmx.core.RelatedAssociation;
008import systems.dmx.core.RelatedTopic;
009import systems.dmx.core.Topic;
010import systems.dmx.core.TopicType;
011import systems.dmx.core.model.AssociationModel;
012import systems.dmx.core.model.AssociationTypeModel;
013import systems.dmx.core.model.SimpleValue;
014import systems.dmx.core.model.TopicModel;
015import systems.dmx.core.model.TopicTypeModel;
016import systems.dmx.core.osgi.PluginActivator;
017import systems.dmx.core.service.DirectivesResponse;
018import systems.dmx.core.service.PluginInfo;
019import systems.dmx.core.service.Transactional;
020import systems.dmx.core.util.IdList;
021
022import org.codehaus.jettison.json.JSONException;
023import org.codehaus.jettison.json.JSONObject;
024
025import javax.ws.rs.GET;
026import javax.ws.rs.POST;
027import javax.ws.rs.PUT;
028import javax.ws.rs.DELETE;
029import javax.ws.rs.Consumes;
030import javax.ws.rs.Path;
031import javax.ws.rs.PathParam;
032import javax.ws.rs.Produces;
033import javax.ws.rs.QueryParam;
034import javax.ws.rs.core.Context;
035
036import javax.servlet.http.HttpServletRequest;
037
038import java.util.List;
039import java.util.logging.Level;
040import java.util.logging.Logger;
041
042
043
044/**
045 * REST API for {@link systems.dmx.core.service.CoreService}.
046 */
047@Path("/core")
048@Consumes("application/json")
049@Produces("application/json")
050public class WebservicePlugin extends PluginActivator {
051
052    // ---------------------------------------------------------------------------------------------- Instance Variables
053
054    @Context
055    private HttpServletRequest request;
056
057    private Messenger me = new Messenger("systems.dmx.webclient");
058
059    private Logger logger = Logger.getLogger(getClass().getName());
060
061    // -------------------------------------------------------------------------------------------------- Public Methods
062
063
064
065    // === Topics ===
066
067    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
068    @GET
069    @Path("/topic/{id}")
070    public Topic getTopic(@PathParam("id") long topicId) {
071        return dmx.getTopic(topicId);
072    }
073
074    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
075    @GET
076    @Path("/topic/by_uri/{uri}")
077    public Topic getTopicByUri(@PathParam("uri") String uri) {
078        return dmx.getTopicByUri(uri);
079    }
080
081    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
082    @GET
083    @Path("/topic/by_value/{key}/{value}")
084    public Topic getTopicByValue(@PathParam("key") String key, @PathParam("value") SimpleValue value) {
085        return dmx.getTopicByValue(key, value);
086    }
087
088    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
089    @GET
090    @Path("/topic/multi/by_value/{key}/{value}")
091    public List<Topic> getTopicsByValue(@PathParam("key") String key, @PathParam("value") SimpleValue value) {
092        return dmx.getTopicsByValue(key, value);
093    }
094
095    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
096    @GET
097    @Path("/topic/by_type/{topic_type_uri}")
098    public List<Topic> getTopicsByType(@PathParam("topic_type_uri") String topicTypeUri) {
099        return dmx.getTopicsByType(topicTypeUri);
100    }
101
102    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
103    @GET
104    @Path("/topic")
105    public List<Topic> searchTopics(@QueryParam("search") String searchTerm, @QueryParam("field") String fieldUri) {
106        return dmx.searchTopics(searchTerm, fieldUri);
107    }
108
109    @POST
110    @Path("/topic")
111    @Transactional
112    public DirectivesResponse createTopic(TopicModel model) {
113        return new DirectivesResponse(dmx.createTopic(model));
114    }
115
116    @PUT
117    @Path("/topic/{id}")
118    @Transactional
119    public DirectivesResponse updateTopic(@PathParam("id") long topicId, TopicModel model) {
120        if (model.getId() != -1 && topicId != model.getId()) {
121            throw new RuntimeException("ID mismatch in update request");
122        }
123        model.setId(topicId);
124        dmx.updateTopic(model);
125        return new DirectivesResponse();
126    }
127
128    @DELETE
129    @Path("/topic/{id}")
130    @Transactional
131    public DirectivesResponse deleteTopic(@PathParam("id") long topicId) {
132        dmx.deleteTopic(topicId);
133        return new DirectivesResponse();
134    }
135
136
137
138    // === Associations ===
139
140    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
141    @GET
142    @Path("/association/{id}")
143    public Association getAssociation(@PathParam("id") long assocId) {
144        return dmx.getAssociation(assocId);
145    }
146
147    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
148    @GET
149    @Path("/assoc/by_value/{key}/{value}")
150    public Association getAssociationByValue(@PathParam("key") String key, @PathParam("value") SimpleValue value) {
151        return dmx.getAssociationByValue(key, value);
152    }
153
154    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
155    @GET
156    @Path("/assoc/multi/by_value/{key}/{value}")
157    public List<Association> getAssociationsByValue(@PathParam("key") String key,
158                                                    @PathParam("value") SimpleValue value) {
159        return dmx.getAssociationsByValue(key, value);
160    }
161
162    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
163    @GET
164    @Path("/association/{assoc_type_uri}/{topic1_id}/{topic2_id}/{role_type1_uri}/{role_type2_uri}")
165    public Association getAssociation(@PathParam("assoc_type_uri") String assocTypeUri,
166                   @PathParam("topic1_id") long topic1Id, @PathParam("topic2_id") long topic2Id,
167                   @PathParam("role_type1_uri") String roleTypeUri1, @PathParam("role_type2_uri") String roleTypeUri2) {
168        return dmx.getAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2);
169    }
170
171    // ---
172
173    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
174    @GET
175    @Path("/association/multiple/{topic1_id}/{topic2_id}")
176    public List<Association> getAssociations(@PathParam("topic1_id") long topic1Id,
177                                             @PathParam("topic2_id") long topic2Id) {
178        return dmx.getAssociations(topic1Id, topic2Id);
179    }
180
181    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
182    @GET
183    @Path("/association/multiple/{topic1_id}/{topic2_id}/{assoc_type_uri}")
184    public List<Association> getAssociations(@PathParam("topic1_id") long topic1Id,
185                                             @PathParam("topic2_id") long topic2Id,
186                                             @PathParam("assoc_type_uri") String assocTypeUri) {
187        return dmx.getAssociations(topic1Id, topic2Id, assocTypeUri);
188    }
189
190    // ---
191
192    @POST
193    @Path("/association")
194    @Transactional
195    public DirectivesResponse createAssociation(AssociationModel model) {
196        return new DirectivesResponse(dmx.createAssociation(model));
197    }
198
199    @PUT
200    @Path("/association/{id}")
201    @Transactional
202    public DirectivesResponse updateAssociation(@PathParam("id") long assocId, AssociationModel model) {
203        if (model.getId() != -1 && assocId != model.getId()) {
204            throw new RuntimeException("ID mismatch in update request");
205        }
206        model.setId(assocId);
207        dmx.updateAssociation(model);
208        return new DirectivesResponse();
209    }
210
211    @DELETE
212    @Path("/association/{id}")
213    @Transactional
214    public DirectivesResponse deleteAssociation(@PathParam("id") long assocId) {
215        dmx.deleteAssociation(assocId);
216        return new DirectivesResponse();
217    }
218
219
220
221    // === Topic Types ===
222
223    @GET
224    @Path("/topictype/{uri}")
225    public TopicType getTopicType(@PathParam("uri") String uri) {
226        return dmx.getTopicType(uri);
227    }
228
229    @GET
230    @Path("/topictype/topic/{id}")
231    public TopicType getTopicTypeImplicitly(@PathParam("id") long topicId) {
232        return dmx.getTopicTypeImplicitly(topicId);
233    }
234
235    @GET
236    @Path("/topictype/all")
237    public List<TopicType> getAllTopicTypes() {
238        return dmx.getAllTopicTypes();
239    }
240
241    @POST
242    @Path("/topictype")
243    @Transactional
244    public TopicType createTopicType(TopicTypeModel model) {
245        TopicType topicType = dmx.createTopicType(model);
246        me.newTopicType(topicType);
247        return topicType;
248    }
249
250    @PUT
251    @Path("/topictype")
252    @Transactional
253    public DirectivesResponse updateTopicType(TopicTypeModel model) {
254        dmx.updateTopicType(model);
255        return new DirectivesResponse();
256    }
257
258    @DELETE
259    @Path("/topictype/{uri}")
260    @Transactional
261    public DirectivesResponse deleteTopicType(@PathParam("uri") String uri) {
262        dmx.deleteTopicType(uri);
263        return new DirectivesResponse();
264    }
265
266
267
268    // === Association Types ===
269
270    @GET
271    @Path("/assoctype/{uri}")
272    public AssociationType getAssociationType(@PathParam("uri") String uri) {
273        return dmx.getAssociationType(uri);
274    }
275
276    @GET
277    @Path("/assoctype/assoc/{id}")
278    public AssociationType getAssociationTypeImplicitly(@PathParam("id") long assocId) {
279        return dmx.getAssociationTypeImplicitly(assocId);
280    }
281
282    @GET
283    @Path("/assoctype/all")
284    public List<AssociationType> getAssociationAllTypes() {
285        return dmx.getAllAssociationTypes();
286    }
287
288    @POST
289    @Path("/assoctype")
290    @Transactional
291    public AssociationType createAssociationType(AssociationTypeModel model) {
292        return dmx.createAssociationType(model);
293    }
294
295    @PUT
296    @Path("/assoctype")
297    @Transactional
298    public DirectivesResponse updateAssociationType(AssociationTypeModel model) {
299        dmx.updateAssociationType(model);
300        return new DirectivesResponse();
301    }
302
303    @DELETE
304    @Path("/assoctype/{uri}")
305    @Transactional
306    public DirectivesResponse deleteAssociationType(@PathParam("uri") String uri) {
307        dmx.deleteAssociationType(uri);
308        return new DirectivesResponse();
309    }
310
311
312
313    // === Role Types ===
314
315    @POST
316    @Path("/roletype")
317    @Transactional
318    public Topic createRoleType(TopicModel model) {
319        return dmx.createRoleType(model);
320    }
321
322
323
324    // === Plugins ===
325
326    @GET
327    @Path("/plugin")
328    public List<PluginInfo> getPluginInfo() {
329        return dmx.getPluginInfo();
330    }
331
332
333
334    // **********************
335    // *** Topic REST API ***
336    // **********************
337
338
339
340    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
341    @GET
342    @Path("/topic/{id}/related_topics")
343    public List<RelatedTopic> getTopicRelatedTopics(@PathParam("id")                     long topicId,
344                                                    @QueryParam("assoc_type_uri")        String assocTypeUri,
345                                                    @QueryParam("my_role_type_uri")      String myRoleTypeUri,
346                                                    @QueryParam("others_role_type_uri")  String othersRoleTypeUri,
347                                                    @QueryParam("others_topic_type_uri") String othersTopicTypeUri) {
348        Topic topic = dmx.getTopic(topicId);
349        return getRelatedTopics(topic, "topic", assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri);
350    }
351
352    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
353    @GET
354    @Path("/topic/{id}/related_assocs")
355    public List<RelatedAssociation> getTopicRelatedAssociations(@PathParam("id")            long topicId,
356                                                       @QueryParam("assoc_type_uri")        String assocTypeUri,
357                                                       @QueryParam("my_role_type_uri")      String myRoleTypeUri,
358                                                       @QueryParam("others_role_type_uri")  String othersRoleTypeUri,
359                                                       @QueryParam("others_assoc_type_uri") String othersAssocTypeUri) {
360        Topic topic = dmx.getTopic(topicId);
361        return getRelatedAssociations(topic, "topic", assocTypeUri, myRoleTypeUri, othersRoleTypeUri,
362            othersAssocTypeUri);
363    }
364
365
366
367    // ****************************
368    // *** Association REST API ***
369    // ****************************
370
371
372
373    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
374    @GET
375    @Path("/association/{id}/related_topics")
376    public List<RelatedTopic> getAssociationRelatedTopics(@PathParam("id")                  long assocId,
377                                                       @QueryParam("assoc_type_uri")        String assocTypeUri,
378                                                       @QueryParam("my_role_type_uri")      String myRoleTypeUri,
379                                                       @QueryParam("others_role_type_uri")  String othersRoleTypeUri,
380                                                       @QueryParam("others_topic_type_uri") String othersTopicTypeUri) {
381        Association assoc = dmx.getAssociation(assocId);
382        return getRelatedTopics(assoc, "association", assocTypeUri, myRoleTypeUri, othersRoleTypeUri,
383            othersTopicTypeUri);
384    }
385
386    // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter
387    @GET
388    @Path("/association/{id}/related_assocs")
389    public List<RelatedAssociation> getAssociationRelatedAssociations(@PathParam("id")      long assocId,
390                                                       @QueryParam("assoc_type_uri")        String assocTypeUri,
391                                                       @QueryParam("my_role_type_uri")      String myRoleTypeUri,
392                                                       @QueryParam("others_role_type_uri")  String othersRoleTypeUri,
393                                                       @QueryParam("others_assoc_type_uri") String othersAssocTypeUri) {
394        Association assoc = dmx.getAssociation(assocId);
395        return getRelatedAssociations(assoc, "association", assocTypeUri, myRoleTypeUri, othersRoleTypeUri,
396            othersAssocTypeUri);
397    }
398
399
400
401    // **********************
402    // *** Multi REST API ***
403    // **********************
404
405
406
407    @DELETE
408    @Path("/topics/{topicIds}")
409    @Transactional
410    public DirectivesResponse deleteTopics(@PathParam("topicIds") IdList topicIds) {
411        return deleteMulti(topicIds, new IdList());
412    }
413
414    @DELETE
415    @Path("/assocs/{assocIds}")
416    @Transactional
417    public DirectivesResponse deleteAssocs(@PathParam("assocIds") IdList assocIds) {
418        return deleteMulti(new IdList(), assocIds);
419    }
420
421    @DELETE
422    @Path("/topics/{topicIds}/assocs/{assocIds}")
423    @Transactional
424    public DirectivesResponse deleteMulti(@PathParam("topicIds") IdList topicIds,
425                                          @PathParam("assocIds") IdList assocIds) {
426        logger.info("topicIds=" + topicIds + ", assocIds=" + assocIds);
427        for (long id : topicIds) {
428            deleteAnyTopic(id);
429        }
430        for (long id : assocIds) {
431            dmx.deleteAssociation(id);
432        }
433        return new DirectivesResponse();
434    }
435
436
437
438    // ***************************
439    // *** WebSockets REST API ***
440    // ***************************
441
442
443
444    @GET
445    @Path("/websockets")
446    public JSONEnabled getWebSocketsConfig() {
447        return new JSONEnabled() {
448            @Override
449            public JSONObject toJSON() {
450                try {
451                    return new JSONObject().put("dmx.websockets.url", dmx.getWebSocketsService().getWebSocketsURL());
452                } catch (JSONException e) {
453                    throw new RuntimeException("Serializing the WebSockets configuration failed", e);
454                }
455            }
456        };
457    }
458
459
460
461    // ------------------------------------------------------------------------------------------------- Private Methods
462
463    private List<RelatedTopic> getRelatedTopics(DMXObject object, String objectInfo, String assocTypeUri,
464                                            String myRoleTypeUri, String othersRoleTypeUri, String othersTopicTypeUri) {
465        String operation = "Fetching related topics of " + objectInfo + " " + object.getId();
466        String paramInfo = "(assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri +
467            "\", othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")";
468        try {
469            logger.info(operation + " " + paramInfo);
470            return object.getRelatedTopics(assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri);
471        } catch (Exception e) {
472            throw new RuntimeException(operation + " failed " + paramInfo, e);
473        }
474    }
475
476    private List<RelatedAssociation> getRelatedAssociations(DMXObject object, String objectInfo,
477                       String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) {
478        String operation = "Fetching related associations of " + objectInfo + " " + object.getId();
479        String paramInfo = "(assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri +
480            "\", othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersAssocTypeUri=\"" + othersAssocTypeUri + "\")";
481        try {
482            logger.info(operation + " " + paramInfo);
483            return object.getRelatedAssociations(assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
484        } catch (Exception e) {
485            throw new RuntimeException(operation + " failed " + paramInfo, e);
486        }
487    }
488
489    // ---
490
491    // TODO: move this logic to dmx.deleteTopic() so that it can delete types as well? (types ARE topics after all)
492    private void deleteAnyTopic(long id) {
493        Topic t = dmx.getTopic(id);
494        String typeUri = t.getTypeUri();
495        if (typeUri.equals("dmx.core.topic_type")) {
496            dmx.deleteTopicType(t.getUri());
497        } else if (typeUri.equals("dmx.core.assoc_type")) {
498            dmx.deleteAssociationType(t.getUri());
499        } else {
500            dmx.deleteTopic(id);
501        }
502    }
503
504    // ------------------------------------------------------------------------------------------------- Private Classes
505
506    private class Messenger {
507
508        private String pluginUri;
509
510        private Messenger(String pluginUri) {
511            this.pluginUri = pluginUri;
512        }
513
514        // ---
515
516        private void newTopicType(TopicType topicType) {
517            try {
518                messageToAllButOne(new JSONObject()
519                    .put("type", "newTopicType")
520                    .put("args", new JSONObject()
521                        .put("topicType", topicType.toJSON())
522                    )
523                );
524            } catch (Exception e) {
525                logger.log(Level.WARNING, "Error while sending a \"newTopicType\" message:", e);
526            }
527        }
528
529        // ---
530
531        private void messageToAllButOne(JSONObject message) {
532            dmx.getWebSocketsService().messageToAllButOne(request, pluginUri, message.toString());
533        }
534    }
535}