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