001 package de.kiezatlas; 002 003 import de.kiezatlas.service.KiezatlasService; 004 005 import de.deepamehta.plugins.accesscontrol.service.AccessControlService; 006 import de.deepamehta.plugins.geomaps.service.GeomapsService; 007 import de.deepamehta.plugins.facets.model.FacetValue; 008 import de.deepamehta.plugins.facets.service.FacetsService; 009 010 import de.deepamehta.core.AssociationDefinition; 011 import de.deepamehta.core.RelatedTopic; 012 import de.deepamehta.core.Topic; 013 import de.deepamehta.core.model.TopicModel; 014 import de.deepamehta.core.model.SimpleValue; 015 import de.deepamehta.core.osgi.PluginActivator; 016 import de.deepamehta.core.service.Cookies; 017 import de.deepamehta.core.service.Inject; 018 import de.deepamehta.core.service.ResultList; 019 import de.deepamehta.core.service.event.PostUpdateTopicListener; 020 import de.deepamehta.core.service.event.PreSendTopicListener; 021 import de.deepamehta.core.util.DeepaMehtaUtils; 022 023 import javax.ws.rs.GET; 024 import javax.ws.rs.Path; 025 import javax.ws.rs.PathParam; 026 import javax.ws.rs.QueryParam; 027 import javax.ws.rs.Produces; 028 import javax.ws.rs.Consumes; 029 030 import java.util.ArrayList; 031 import java.util.Iterator; 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 KiezatlasService, PostUpdateTopicListener, 041 PreSendTopicListener { 042 043 // ------------------------------------------------------------------------------------------------------- Constants 044 045 private static final String TYPE_URI_GEO_OBJECT = "ka2.geo_object"; 046 private static final String TYPE_URI_GEO_OBJECT_NAME = "ka2.geo_object.name"; 047 048 // Website-Geomap association 049 private static final String WEBSITE_GEOMAP = "dm4.core.association"; 050 private static final String ROLE_TYPE_WEBSITE = "dm4.core.default"; // Note: used for both associations 051 private static final String ROLE_TYPE_GEOMAP = "dm4.core.default"; 052 // Website-Facet Types association 053 private static final String WEBSITE_FACET_TYPES = "dm4.core.association"; 054 private static final String ROLE_TYPE_FACET_TYPE = "dm4.core.default"; 055 056 // ---------------------------------------------------------------------------------------------- Instance Variables 057 058 @Inject 059 private GeomapsService geomapsService; 060 061 @Inject 062 private FacetsService facetsService; 063 064 // ### FIXME: must *wait* for the Access Control service but don't actually *consume* it. 065 // This ensures the Kiezatlas types are properly setup for Access Control. ### Still required? 066 067 private Logger logger = Logger.getLogger(getClass().getName()); 068 069 // -------------------------------------------------------------------------------------------------- Public Methods 070 071 072 073 // ********************** 074 // *** Plugin Service *** 075 // ********************** 076 077 078 079 @GET 080 @Path("/geomap/{geomap_id}") 081 @Override 082 public Topic getWebsite(@PathParam("geomap_id") long geomapId) { 083 try { 084 return dms.getTopic(geomapId).getRelatedTopic(WEBSITE_GEOMAP, ROLE_TYPE_WEBSITE, ROLE_TYPE_GEOMAP, 085 "ka2.website"); 086 } catch (Exception e) { 087 throw new RuntimeException("Finding the geomap's website topic failed (geomapId=" + geomapId + ")", e); 088 } 089 } 090 091 @GET 092 @Path("/{website_id}/facets") 093 @Override 094 public ResultList<RelatedTopic> getFacetTypes(@PathParam("website_id") long websiteId) { 095 try { 096 return dms.getTopic(websiteId).getRelatedTopics(WEBSITE_FACET_TYPES, ROLE_TYPE_WEBSITE, 097 ROLE_TYPE_FACET_TYPE, "dm4.core.topic_type", 0); 098 } catch (Exception e) { 099 throw new RuntimeException("Finding the website's facet types failed (websiteId=" + websiteId + ")", e); 100 } 101 } 102 103 @GET 104 @Path("/criteria") 105 @Override 106 public List<Topic> getAllCriteria() { 107 List<Topic> criteria = dms.getTopics("uri", new SimpleValue("ka2.criteria.*")); 108 // remove facet types 109 Iterator<Topic> i = criteria.iterator(); 110 while (i.hasNext()) { 111 Topic crit = i.next(); 112 if (crit.getUri().endsWith(".facet")) { 113 i.remove(); 114 } 115 } 116 // 117 return criteria; 118 } 119 120 @GET 121 @Path("/geomap/{geomap_id}/objects") 122 @Override 123 public List<Topic> getGeoObjects(@PathParam("geomap_id") long geomapId) { 124 try { 125 return fetchGeoObjects(geomapId); 126 } catch (Exception e) { 127 throw new RuntimeException("Fetching the geomap's geo objects failed (geomapId=" + geomapId + ")", e); 128 } 129 } 130 131 // Note: the "include_childs" query paramter is handled by the core's JerseyResponseFilter 132 @GET 133 @Path("/category/{id}/objects") 134 @Override 135 public List<RelatedTopic> getGeoObjectsByCategory(@PathParam("id") long categoryId) { 136 return dms.getTopic(categoryId).getRelatedTopics("dm4.core.aggregation", "dm4.core.child", "dm4.core.parent", 137 TYPE_URI_GEO_OBJECT, 0).getItems(); 138 } 139 140 @GET 141 @Path("/geoobject") 142 @Override 143 public GeoObjects searchGeoObjects(@QueryParam("search") String searchTerm, @QueryParam("clock") long clock) { 144 GeoObjects result = new GeoObjects(clock); 145 for (Topic geoObjectName : dms.searchTopics("*" + searchTerm + "*", TYPE_URI_GEO_OBJECT_NAME)) { 146 result.add(getGeoObject(geoObjectName)); 147 } 148 return result; 149 } 150 151 @GET 152 @Path("/category/objects") 153 @Override 154 public GroupedGeoObjects searchCategories(@QueryParam("search") String searchTerm, 155 @QueryParam("clock") long clock) { 156 GroupedGeoObjects result = new GroupedGeoObjects(clock); 157 for (Topic criteria : getAllCriteria()) { 158 for (Topic category : dms.searchTopics("*" + searchTerm + "*", criteria.getUri())) { 159 result.add(criteria, category, getGeoObjectsByCategory(category.getId())); 160 } 161 } 162 return result; 163 } 164 165 166 167 // ******************************** 168 // *** Listener Implementations *** 169 // ******************************** 170 171 172 173 @Override 174 public void preSendTopic(Topic topic) { 175 if (!topic.getTypeUri().equals(TYPE_URI_GEO_OBJECT)) { 176 return; 177 } 178 // 179 ResultList<RelatedTopic> facetTypes = getFacetTypes(); 180 if (facetTypes == null) { 181 return; 182 } 183 // 184 enrichWithFacets(topic, facetTypes); 185 } 186 187 @Override 188 public void postUpdateTopic(Topic topic, TopicModel newModel, TopicModel oldModel) { 189 if (!topic.getTypeUri().equals(TYPE_URI_GEO_OBJECT)) { 190 return; 191 } 192 // 193 ResultList<RelatedTopic> facetTypes = getFacetTypes(); 194 if (facetTypes == null) { 195 return; 196 } 197 // 198 updateFacets(topic, facetTypes, newModel); 199 } 200 201 // ------------------------------------------------------------------------------------------------- Private Methods 202 203 204 205 206 // === Enrich with facets === 207 208 private void enrichWithFacets(Topic geoObject, ResultList<RelatedTopic> facetTypes) { 209 for (Topic facetType : facetTypes) { 210 String facetTypeUri = facetType.getUri(); 211 if (!isMultiFacet(facetTypeUri)) { 212 enrichWithSingleFacet(geoObject, facetTypeUri); 213 } else { 214 enrichWithMultiFacet(geoObject, facetTypeUri); 215 } 216 } 217 } 218 219 // --- 220 221 private void enrichWithSingleFacet(Topic geoObject, String facetTypeUri) { 222 Topic facetValue = facetsService.getFacet(geoObject, facetTypeUri); 223 // Note: facetValue is null in 2 cases: 224 // 1) The geo object has just been created (no update yet) 225 // 2) The geo object has been created outside a geomap and then being revealed in a geomap. 226 if (facetValue == null) { 227 logger.info("### Enriching geo object " + geoObject.getId() + " with its \"" + facetTypeUri + 228 "\" facet value ABORTED -- no such facet in DB"); 229 return; 230 } 231 // 232 logger.info("### Enriching geo object " + geoObject.getId() + " with its \"" + facetTypeUri + 233 "\" facet value (" + facetValue + ")"); 234 geoObject.getChildTopics().getModel().put(facetValue.getTypeUri(), facetValue.getModel()); 235 } 236 237 private void enrichWithMultiFacet(Topic geoObject, String facetTypeUri) { 238 ResultList<RelatedTopic> facetValues = facetsService.getFacets(geoObject, facetTypeUri); 239 logger.info("### Enriching geo object " + geoObject.getId() + " with its \"" + facetTypeUri + 240 "\" facet values (" + facetValues + ")"); 241 String childTypeUri = getChildTypeUri(facetTypeUri); 242 // Note: we set the facet values at once (using put()) instead of iterating (and using add()) as after an geo 243 // object update request the facet values are already set. Using add() would result in having the values twice. 244 geoObject.getChildTopics().getModel().put(childTypeUri, DeepaMehtaUtils.toTopicModels(facetValues)); 245 } 246 247 248 249 // === Update facets === 250 251 private void updateFacets(Topic geoObject, ResultList<RelatedTopic> facetTypes, TopicModel newModel) { 252 for (Topic facetType : facetTypes) { 253 String facetTypeUri = facetType.getUri(); 254 String childTypeUri = getChildTypeUri(facetTypeUri); 255 if (!isMultiFacet(facetTypeUri)) { 256 TopicModel facetValue = newModel.getChildTopicsModel().getTopic(childTypeUri); 257 logger.info("### Storing facet of type \"" + facetTypeUri + "\" for geo object " + geoObject.getId() + 258 " (facetValue=" + facetValue + ")"); 259 FacetValue value = new FacetValue(childTypeUri).put(facetValue); 260 facetsService.updateFacet(geoObject, facetTypeUri, value); 261 } else { 262 List<TopicModel> facetValues = newModel.getChildTopicsModel().getTopics(childTypeUri); 263 logger.info("### Storing facets of type \"" + facetTypeUri + "\" for geo object " + geoObject.getId() + 264 " (facetValues=" + facetValues + ")"); 265 FacetValue value = new FacetValue(childTypeUri).put(facetValues); 266 facetsService.updateFacet(geoObject, facetTypeUri, value); 267 } 268 } 269 } 270 271 272 273 // === Helper === 274 275 /** 276 * Returns the facet types for the current topicmap, or null if the facet types can't be determined. 277 * There can be several reasons for the latter: 278 * a) there is no "current topicmap". This can be the case with 3rd-party clients. 279 * b) the current topicmap is not a geomap. 280 * c) the geomap is not part of a Kiezatlas Website. 281 * 282 * @return The facet types (as a result set, may be empty), or <code>null</code>. 283 */ 284 private ResultList<RelatedTopic> getFacetTypes() { 285 Cookies cookies = Cookies.get(); 286 if (!cookies.has("dm4_topicmap_id")) { 287 logger.info("### Finding geo object facet types ABORTED -- topicmap is unknown (no \"dm4_topicmap_id\" " + 288 "cookie was sent)"); 289 return null; 290 } 291 // 292 long topicmapId = cookies.getLong("dm4_topicmap_id"); 293 if (!isGeomap(topicmapId)) { 294 logger.info("### Finding geo object facet types for topicmap " + topicmapId + " ABORTED -- not a geomap"); 295 return null; 296 } 297 // 298 Topic website = getWebsite(topicmapId); 299 if (website == null) { 300 logger.info("### Finding geo object facet types for geomap " + topicmapId + " ABORTED -- not part of a " + 301 "Kiezatlas website"); 302 return null; 303 } 304 // 305 logger.info("### Finding geo object facet types for geomap " + topicmapId); 306 return getFacetTypes(website.getId()); 307 } 308 309 private List<Topic> fetchGeoObjects(long geomapId) { 310 List<Topic> geoObjects = new ArrayList(); 311 for (TopicModel geoCoord : geomapsService.getGeomap(geomapId)) { 312 Topic geoObject = geomapsService.getDomainTopic(geoCoord.getId()); 313 geoObjects.add(geoObject); 314 // ### TODO: optimization. Include only name and address in returned geo objects. 315 // ### For the moment the entire objects are returned, including composite values and facets. 316 } 317 return geoObjects; 318 } 319 320 // --- 321 322 private Topic getGeoObject(Topic geoObjectName) { 323 return geoObjectName.getRelatedTopic("dm4.core.composition", "dm4.core.child", "dm4.core.parent", 324 TYPE_URI_GEO_OBJECT); // ### TODO: Core API should provide type-driven navigation 325 } 326 327 private boolean isGeomap(long topicmapId) { 328 Topic topicmap = dms.getTopic(topicmapId); 329 String rendererUri = topicmap.getChildTopics().getString("dm4.topicmaps.topicmap_renderer_uri"); 330 return rendererUri.equals("dm4.geomaps.geomap_renderer"); 331 } 332 333 // --- 334 335 // ### FIXME: there is a copy in FacetsPlugin.java 336 private boolean isMultiFacet(String facetTypeUri) { 337 return getAssocDef(facetTypeUri).getChildCardinalityUri().equals("dm4.core.many"); 338 } 339 340 // ### FIXME: there is a copy in FacetsPlugin.java 341 private String getChildTypeUri(String facetTypeUri) { 342 return getAssocDef(facetTypeUri).getChildTypeUri(); 343 } 344 345 // ### FIXME: there is a copy in FacetsPlugin.java 346 private AssociationDefinition getAssocDef(String facetTypeUri) { 347 // Note: a facet type has exactly *one* association definition 348 return dms.getTopicType(facetTypeUri).getAssocDefs().iterator().next(); 349 } 350 }