001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationType;
005    import de.deepamehta.core.DeepaMehtaObject;
006    import de.deepamehta.core.ChildTopics;
007    import de.deepamehta.core.RelatedTopic;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.TopicType;
010    import de.deepamehta.core.service.Directives;
011    
012    import com.sun.jersey.spi.container.ContainerRequest;
013    import com.sun.jersey.spi.container.ContainerResponse;
014    import com.sun.jersey.spi.container.ContainerResponseFilter;
015    
016    import java.lang.reflect.Type;
017    import java.lang.reflect.ParameterizedType;
018    import java.util.List;
019    import java.util.logging.Logger;
020    
021    
022    
023    class JerseyResponseFilter implements ContainerResponseFilter {
024    
025        // ---------------------------------------------------------------------------------------------- Instance Variables
026    
027        private EmbeddedService dms;
028    
029        private Logger logger = Logger.getLogger(getClass().getName());
030    
031        // ---------------------------------------------------------------------------------------------------- Constructors
032    
033        JerseyResponseFilter(EmbeddedService dms) {
034            this.dms = dms;
035        }
036    
037        // -------------------------------------------------------------------------------------------------- Public Methods
038    
039        @Override
040        public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
041            try {
042                dms.fireEvent(CoreEvent.SERVICE_RESPONSE_FILTER, response);
043                //
044                Object entity = response.getEntity();
045                boolean includeChilds = getIncludeChilds(request);
046                boolean includeAssocChilds = getIncludeAssocChilds(request);
047                if (entity != null) {
048                    //
049                    // 1) Loading child topics
050                    if (entity instanceof DeepaMehtaObject) {
051                        loadChildTopics((DeepaMehtaObject) entity, includeChilds, includeAssocChilds);
052                    } else if (isIterable(response, DeepaMehtaObject.class)) {
053                        loadChildTopics((Iterable<DeepaMehtaObject>) entity, includeChilds, includeAssocChilds);
054                    }
055                    //
056                    // 2) Firing PRE_SEND events
057                    if (entity instanceof TopicType) {          // Note: must take precedence over topic
058                        firePreSend((TopicType) entity);
059                    } else if (entity instanceof AssociationType) {
060                        firePreSend((AssociationType) entity);  // Note: must take precedence over topic
061                    } else if (entity instanceof Topic) {
062                        firePreSend((Topic) entity);
063                    } else if (entity instanceof Association) {
064                        firePreSend((Association) entity);
065                    } else if (entity instanceof Directives) {
066                        // Note: some plugins rely on the PRE_SEND event in order to enrich updated objects, others don't.
067                        // E.g. the Access Control plugin must enrich updated objects with permission information.
068                        // ### TODO: check if this is still required. Meanwhile permissions are not an enrichment anymore.
069                        // ### Update: Yes, it is still required, e.g. by the Time plugin when enriching with timestamps.
070                        firePreSend((Directives) entity);
071                    } else if (isIterable(response, TopicType.class)) {
072                        firePreSendTopicTypes((Iterable<TopicType>) entity);
073                    } else if (isIterable(response, AssociationType.class)) {
074                        firePreSendAssociationTypes((Iterable<AssociationType>) entity);
075                    } else if (isIterable(response, Topic.class)) {
076                        firePreSendTopics((Iterable<Topic>) entity);
077                    // ### FIXME: for Iterable<Association> no PRE_SEND_ASSOCIATION events are fired
078                    }
079                }
080                //
081                logger.fine("### Removing tread-local directives");
082                Directives.remove();
083                //
084                return response;
085            } catch (Exception e) {
086                throw new RuntimeException("Jersey response filtering failed", e);
087            }
088        }
089    
090        // ------------------------------------------------------------------------------------------------- Private Methods
091    
092        // === Loading child topics ===
093    
094        private void loadChildTopics(DeepaMehtaObject object, boolean includeChilds,
095                                                              boolean includeAssocChilds) {
096            if (includeChilds) {
097                object.loadChildTopics();
098                if (includeAssocChilds) {
099                    loadRelatingAssociationChildTopics(object);
100                }
101            }
102        }
103    
104        private void loadChildTopics(Iterable<DeepaMehtaObject> objects, boolean includeChilds,
105                                                                         boolean includeAssocChilds) {
106            if (includeChilds) {
107                for (DeepaMehtaObject object : objects) {
108                    object.loadChildTopics();
109                }
110                if (includeAssocChilds) {
111                    for (DeepaMehtaObject object : objects) {
112                        loadRelatingAssociationChildTopics(object);
113                    }
114                }
115            }
116        }
117    
118        // ---
119    
120        private void loadRelatingAssociationChildTopics(DeepaMehtaObject object) {
121            ChildTopics childTopics = object.getChildTopics();
122            for (String childTypeUri : childTopics) {
123                Object value = childTopics.get(childTypeUri);
124                if (value instanceof RelatedTopic) {
125                    RelatedTopic childTopic = (RelatedTopic) value;
126                    childTopic.getRelatingAssociation().loadChildTopics();
127                    loadRelatingAssociationChildTopics(childTopic);         // recursion
128                } else if (value instanceof List) {
129                    for (RelatedTopic childTopic : (List<RelatedTopic>) value) {
130                        childTopic.getRelatingAssociation().loadChildTopics();
131                        loadRelatingAssociationChildTopics(childTopic);     // recursion
132                    }
133                } else {
134                    throw new RuntimeException("Unexpected \"" + childTypeUri + "\" value in ChildTopics: " + value);
135                }
136            }
137        }
138    
139        // === Firing PRE_SEND events ===
140    
141        private void firePreSend(Topic topic) {
142            dms.fireEvent(CoreEvent.PRE_SEND_TOPIC, topic);
143        }
144    
145        private void firePreSend(Association assoc) {
146            dms.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION, assoc);
147        }
148    
149        private void firePreSend(TopicType topicType) {
150            dms.fireEvent(CoreEvent.PRE_SEND_TOPIC_TYPE, topicType);
151        }
152    
153        private void firePreSend(AssociationType assocType) {
154            dms.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION_TYPE, assocType);
155        }
156    
157        private void firePreSend(Directives directives) {
158            for (Directives.Entry entry : directives) {
159                switch (entry.dir) {
160                case UPDATE_TOPIC:
161                    firePreSend((Topic) entry.arg);
162                    break;
163                case UPDATE_ASSOCIATION:
164                    firePreSend((Association) entry.arg);
165                    break;
166                case UPDATE_TOPIC_TYPE:
167                    firePreSend((TopicType) entry.arg);
168                    break;
169                case UPDATE_ASSOCIATION_TYPE:
170                    firePreSend((AssociationType) entry.arg);
171                    break;
172                }
173            }
174        }
175    
176        private void firePreSendTopics(Iterable<Topic> topics) {
177            for (Topic topic : topics) {
178                firePreSend(topic);
179            }
180        }
181    
182        private void firePreSendTopicTypes(Iterable<TopicType> topicTypes) {
183            for (TopicType topicType : topicTypes) {
184                firePreSend(topicType);
185            }
186        }
187    
188        private void firePreSendAssociationTypes(Iterable<AssociationType> assocTypes) {
189            for (AssociationType assocType : assocTypes) {
190                firePreSend(assocType);
191            }
192        }
193    
194        // === Helper ===
195    
196        private boolean isIterable(ContainerResponse response, Class elementType) {
197            Type genericType = response.getEntityType();
198            if (genericType instanceof ParameterizedType) {
199                Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
200                Class<?> type = response.getEntity().getClass();
201                if (typeArgs.length == 1 && Iterable.class.isAssignableFrom(type) &&
202                                               elementType.isAssignableFrom((Class) typeArgs[0])) {
203                    return true;
204                }
205            }
206            return false;
207        }
208    
209        // ---
210    
211        private boolean getIncludeChilds(ContainerRequest request) {
212            return getBooleanQueryParameter(request, "include_childs");
213        }
214    
215        private boolean getIncludeAssocChilds(ContainerRequest request) {
216            return getBooleanQueryParameter(request, "include_assoc_childs");
217        }
218    
219        // ---
220    
221        private boolean getBooleanQueryParameter(ContainerRequest request, String param) {
222            return Boolean.parseBoolean(request.getQueryParameters().getFirst(param));
223        }    
224    }