001 package de.deepamehta.plugins.kiezatlas; 002 003 import de.deepamehta.plugins.geomaps.service.GeomapsService; 004 import de.deepamehta.plugins.facets.service.FacetsService; 005 006 import de.deepamehta.core.AssociationDefinition; 007 import de.deepamehta.core.RelatedTopic; 008 import de.deepamehta.core.Topic; 009 import de.deepamehta.core.model.TopicModel; 010 import de.deepamehta.core.osgi.PluginActivator; 011 import de.deepamehta.core.service.ClientState; 012 import de.deepamehta.core.service.Directives; 013 import de.deepamehta.core.service.PluginService; 014 import de.deepamehta.core.service.ResultList; 015 import de.deepamehta.core.service.annotation.ConsumesService; 016 import de.deepamehta.core.service.event.PostUpdateTopicListener; 017 import de.deepamehta.core.service.event.PreSendTopicListener; 018 019 import javax.ws.rs.GET; 020 import javax.ws.rs.PUT; 021 import javax.ws.rs.POST; 022 import javax.ws.rs.DELETE; 023 import javax.ws.rs.HeaderParam; 024 import javax.ws.rs.Path; 025 import javax.ws.rs.PathParam; 026 import javax.ws.rs.Produces; 027 import javax.ws.rs.Consumes; 028 import javax.ws.rs.WebApplicationException; 029 030 import java.io.InputStream; 031 import java.util.ArrayList; 032 import java.util.List; 033 import java.util.logging.Logger; 034 035 036 037 @Path("/site") 038 @Consumes("application/json") 039 @Produces("application/json") 040 public class KiezatlasPlugin extends PluginActivator implements PostUpdateTopicListener, PreSendTopicListener { 041 042 // ------------------------------------------------------------------------------------------------------- Constants 043 044 // Website-Geomap association 045 private static final String WEBSITE_GEOMAP = "dm4.core.association"; 046 private static final String ROLE_TYPE_WEBSITE = "dm4.core.default"; // Note: used for both associations 047 private static final String ROLE_TYPE_GEOMAP = "dm4.core.default"; 048 // Website-Facet Types association 049 private static final String WEBSITE_FACET_TYPES = "dm4.core.association"; 050 // private static final String ROLE_TYPE_WEBSITE = "dm4.core.default"; 051 private static final String ROLE_TYPE_FACET_TYPE = "dm4.core.default"; 052 053 // ---------------------------------------------------------------------------------------------- Instance Variables 054 055 private GeomapsService geomapsService; 056 private FacetsService facetsService; 057 058 private Logger logger = Logger.getLogger(getClass().getName()); 059 060 // -------------------------------------------------------------------------------------------------- Public Methods 061 062 063 064 065 // ********************** 066 // *** Plugin Service *** 067 // ********************** 068 069 070 071 @GET 072 @Path("/{url}") 073 @Produces("text/html") 074 public InputStream launchWebclient() { 075 // Note: the template parameters are evaluated at client-side 076 try { 077 return dms.getPlugin("de.deepamehta.webclient").getResourceAsStream("web/index.html"); 078 } catch (Exception e) { 079 throw new WebApplicationException(e); 080 } 081 } 082 083 @GET 084 @Path("/geomap/{geomap_id}") 085 public Topic getWebsite(@PathParam("geomap_id") long geomapId) { 086 try { 087 return dms.getTopic(geomapId, false).getRelatedTopic(WEBSITE_GEOMAP, ROLE_TYPE_WEBSITE, 088 ROLE_TYPE_GEOMAP, "dm4.kiezatlas.website", false, false); 089 } catch (Exception e) { 090 throw new WebApplicationException(new RuntimeException("Finding the geomap's website topic failed " + 091 "(geomapId=" + geomapId + ")", e)); 092 } 093 } 094 095 @GET 096 @Path("/{website_id}/facets") 097 public ResultList<RelatedTopic> getFacetTypes(@PathParam("website_id") long websiteId) { 098 try { 099 return dms.getTopic(websiteId, false).getRelatedTopics(WEBSITE_FACET_TYPES, ROLE_TYPE_WEBSITE, 100 ROLE_TYPE_FACET_TYPE, "dm4.core.topic_type", false, false, 0); 101 } catch (Exception e) { 102 throw new WebApplicationException(new RuntimeException("Finding the website's facet types failed " + 103 "(websiteId=" + websiteId + ")", e)); 104 } 105 } 106 107 @GET 108 @Path("/geomap/{geomap_id}/objects") 109 public List<Topic> getGeoObjects(@PathParam("geomap_id") long geomapId) { 110 try { 111 return fetchGeoObjects(geomapId); 112 } catch (Exception e) { 113 throw new WebApplicationException(new RuntimeException("Fetching the geomap's geo objects failed " + 114 "(geomapId=" + geomapId + ")", e)); 115 } 116 } 117 118 119 120 // **************************** 121 // *** Hook Implementations *** 122 // **************************** 123 124 125 126 /** 127 * Note: we *wait* for the Access Control service but we don't actually *consume* it. 128 * This ensures the Kiezatlas types are properly setup for Access Control. 129 */ 130 @Override 131 @ConsumesService({ 132 "de.deepamehta.plugins.geomaps.service.GeomapsService", 133 "de.deepamehta.plugins.facets.service.FacetsService", 134 "de.deepamehta.plugins.accesscontrol.service.AccessControlService" 135 }) 136 public void serviceArrived(PluginService service) { 137 if (service instanceof GeomapsService) { 138 geomapsService = (GeomapsService) service; 139 } else if (service instanceof FacetsService) { 140 facetsService = (FacetsService) service; 141 } 142 } 143 144 @Override 145 public void serviceGone(PluginService service) { 146 if (service == geomapsService) { 147 geomapsService = null; 148 } else if (service == facetsService) { 149 facetsService = null; 150 } 151 } 152 153 154 155 // ******************************** 156 // *** Listener Implementations *** 157 // ******************************** 158 159 160 161 @Override 162 public void preSendTopic(Topic topic, ClientState clientState) { 163 if (!topic.getTypeUri().equals("dm4.kiezatlas.geo_object")) { 164 return; 165 } 166 // 167 ResultList<RelatedTopic> facetTypes = getFacetTypes(clientState); 168 if (facetTypes == null) { 169 return; 170 } 171 // 172 enrichWithFacets(topic, facetTypes); 173 } 174 175 @Override 176 public void postUpdateTopic(Topic topic, TopicModel newModel, TopicModel oldModel, ClientState clientState, 177 Directives directives) { 178 if (!topic.getTypeUri().equals("dm4.kiezatlas.geo_object")) { 179 return; 180 } 181 // 182 ResultList<RelatedTopic> facetTypes = getFacetTypes(clientState); 183 if (facetTypes == null) { 184 return; 185 } 186 // 187 updateFacets(topic, facetTypes, newModel, clientState, directives); 188 } 189 190 // ------------------------------------------------------------------------------------------------- Private Methods 191 192 193 194 195 // === Enrich with facets === 196 197 private void enrichWithFacets(Topic topic, ResultList<RelatedTopic> facetTypes) { 198 for (Topic facetType : facetTypes) { 199 String facetTypeUri = facetType.getUri(); 200 String cardinalityUri = getAssocDef(facetTypeUri).getChildCardinalityUri(); 201 if (cardinalityUri.equals("dm4.core.one")) { 202 enrichWithSingleFacet(topic, facetTypeUri); 203 } else if (cardinalityUri.equals("dm4.core.many")) { 204 enrichWithMultiFacet(topic, facetTypeUri); 205 } else { 206 throw new RuntimeException("\"" + cardinalityUri + "\" is an unsupported cardinality URI"); 207 } 208 } 209 } 210 211 // --- 212 213 private void enrichWithSingleFacet(Topic topic, String facetTypeUri) { 214 Topic facet = facetsService.getFacet(topic, facetTypeUri); 215 // Note: facet is null in 2 cases: 216 // 1) The geo object has just been created (no update yet) 217 // 2) The geo object has been created outside a geomap and then being revealed in a geomap. 218 if (facet == null) { 219 logger.info("### Enriching geo object " + topic.getId() + " with its \"" + facetTypeUri + "\" facet " + 220 "ABORTED -- no such facet in DB"); 221 return; 222 } 223 // 224 logger.info("### Enriching geo object " + topic.getId() + " with its \"" + facetTypeUri + "\" facet (" + 225 facet + ")"); 226 topic.getCompositeValue().getModel().put(facet.getTypeUri(), facet.getModel()); 227 } 228 229 private void enrichWithMultiFacet(Topic topic, String facetTypeUri) { 230 List<RelatedTopic> facets = facetsService.getFacets(topic, facetTypeUri); 231 logger.info("### Enriching geo object " + topic.getId() + " with its \"" + facetTypeUri + "\" facets (" + 232 facets + ")"); 233 for (Topic facet : facets) { 234 topic.getCompositeValue().getModel().add(facet.getTypeUri(), facet.getModel()); 235 } 236 } 237 238 239 240 // === Update facets === 241 242 private void updateFacets(Topic topic, ResultList<RelatedTopic> facetTypes, TopicModel newModel, 243 ClientState clientState, Directives directives) { 244 for (Topic facetType : facetTypes) { 245 String facetTypeUri = facetType.getUri(); 246 AssociationDefinition assocDef = getAssocDef(facetTypeUri); 247 String childTypeUri = assocDef.getChildTypeUri(); 248 String cardinalityUri = assocDef.getChildCardinalityUri(); 249 if (cardinalityUri.equals("dm4.core.one")) { 250 TopicModel facetValue = newModel.getCompositeValueModel().getTopic(childTypeUri); 251 logger.info("### Storing facet of type \"" + facetTypeUri + "\" for geo object " + topic.getId() + 252 " (facetValue=" + facetValue + ")"); 253 facetsService.updateFacet(topic, facetTypeUri, facetValue, clientState, directives); 254 } else if (cardinalityUri.equals("dm4.core.many")) { 255 List<TopicModel> facetValues = newModel.getCompositeValueModel().getTopics(childTypeUri); 256 logger.info("### Storing facets of type \"" + facetTypeUri + "\" for geo object " + topic.getId() + 257 " (facetValues=" + facetValues + ")"); 258 facetsService.updateFacets(topic, facetTypeUri, facetValues, clientState, directives); 259 } else { 260 throw new RuntimeException("\"" + cardinalityUri + "\" is an unsupported cardinality URI"); 261 } 262 } 263 } 264 265 266 267 // === Helper === 268 269 /** 270 * Determines the facet types of the selected topicmap. 271 * 272 * @return The facet types (as a result set, may be empty), or <code>null</code> if 273 * a) the selected topicmap is not a geomap, or 274 * b) the geomap is not associated to a Kiezatlas Website. 275 */ 276 private ResultList<RelatedTopic> getFacetTypes(ClientState clientState) { 277 long topicmapId = clientState.getLong("dm4_topicmap_id"); 278 // 279 if (!isGeomap(topicmapId)) { 280 logger.info("### Finding geo object facet types for topicmap " + topicmapId + " ABORTED -- not a geomap"); 281 return null; 282 } 283 // 284 Topic website = getWebsite(topicmapId); 285 if (website == null) { 286 logger.info("### Finding geo object facet types for geomap " + topicmapId + " ABORTED -- not part of a " + 287 "Kiezatlas website"); 288 return null; 289 } 290 // 291 logger.info("### Finding geo object facet types for geomap " + topicmapId); 292 return getFacetTypes(website.getId()); 293 } 294 295 private List<Topic> fetchGeoObjects(long geomapId) { 296 List<Topic> geoObjects = new ArrayList(); 297 ResultList<RelatedTopic> geomapTopics = geomapsService.getGeomapTopics(geomapId); 298 for (RelatedTopic topic : geomapTopics) { 299 Topic geoTopic = geomapsService.getGeoTopic(topic.getId()); 300 geoObjects.add(geoTopic); 301 // ### TODO: optimization. Include only name and address in returned geo objects. 302 // ### For the moment the entire objects are returned, including composite values and facets. 303 } 304 return geoObjects; 305 } 306 307 // --- 308 309 private boolean isGeomap(long topicmapId) { 310 Topic topicmap = dms.getTopic(topicmapId, true); 311 String rendererUri = topicmap.getCompositeValue().getString("dm4.topicmaps.topicmap_renderer_uri"); 312 return rendererUri.equals("dm4.geomaps.geomap_renderer"); 313 } 314 315 // ### FIXME: there is a copy in FacetsPlugin.java 316 private AssociationDefinition getAssocDef(String facetTypeUri) { 317 // Note: a facet type has exactly *one* association definition 318 return dms.getTopicType(facetTypeUri).getAssocDefs().iterator().next(); 319 } 320 }