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 }