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    }