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