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 }