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