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}