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    }