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.Directives;
011import de.deepamehta.core.service.DirectivesResponse;
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 EventManager em;
029
030    private Logger logger = Logger.getLogger(getClass().getName());
031
032    // ---------------------------------------------------------------------------------------------------- Constructors
033
034    JerseyResponseFilter(EventManager em) {
035        this.em = em;
036    }
037
038    // -------------------------------------------------------------------------------------------------- Public Methods
039
040    @Override
041    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
042        try {
043            em.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                // ### TODO: move to Webservice module?
052                if (entity instanceof DeepaMehtaObject) {
053                    loadChildTopics((DeepaMehtaObject) entity, includeChilds, includeAssocChilds);
054                } else if (isIterable(response, DeepaMehtaObject.class)) {
055                    loadChildTopics((Iterable<DeepaMehtaObject>) entity, includeChilds, includeAssocChilds);
056                }
057                //
058                // 2) Firing PRE_SEND events
059                // ### TODO: move to Webservice module?
060                if (entity instanceof DeepaMehtaObject) {
061                    firePreSend((DeepaMehtaObject) entity);
062                } else if (isIterable(response, DeepaMehtaObject.class)) {
063                    firePreSend((Iterable<DeepaMehtaObject>) entity);
064                } else if (entity instanceof DirectivesResponse) {
065                    firePreSend(((DirectivesResponse) entity).getObject());
066                    //
067                    // Note: some plugins rely on the PRE_SEND event to be fired for the individual DeepaMehta
068                    // objects contained in the set of directives. E.g. the Time plugin enriches updated objects
069                    // with  timestamps. The timestamps in turn are needed at client-side by the Caching plugin
070                    // in order to issue conditional PUT requests.
071                    // ### TODO: don't fire PRE_SEND events for the individual directives but only for the wrapped
072                    // DeepaMehtaObject? Let the update() Core Service calls return the updated object?
073                    firePreSend(((DirectivesResponse) entity).getDirectives());
074                }
075            }
076            //
077            Directives.remove();
078            //
079            return response;
080        } catch (Exception e) {
081            throw new RuntimeException("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                                                          boolean includeAssocChilds) {
091        if (includeChilds) {
092            object.loadChildTopics();
093            if (includeAssocChilds) {
094                loadRelatingAssociationChildTopics(object);
095            }
096        }
097    }
098
099    private void loadChildTopics(Iterable<DeepaMehtaObject> objects, boolean includeChilds,
100                                                                     boolean includeAssocChilds) {
101        for (DeepaMehtaObject object : objects) {
102            loadChildTopics(object, includeChilds, includeChilds);
103        }
104    }
105
106    // ---
107
108    private void loadRelatingAssociationChildTopics(DeepaMehtaObject object) {
109        ChildTopics childTopics = object.getChildTopics();
110        for (String childTypeUri : childTopics) {
111            Object value = childTopics.get(childTypeUri);
112            if (value instanceof RelatedTopic) {
113                RelatedTopic childTopic = (RelatedTopic) value;
114                childTopic.getRelatingAssociation().loadChildTopics();
115                loadRelatingAssociationChildTopics(childTopic);         // recursion
116            } else if (value instanceof List) {
117                for (RelatedTopic childTopic : (List<RelatedTopic>) value) {
118                    childTopic.getRelatingAssociation().loadChildTopics();
119                    loadRelatingAssociationChildTopics(childTopic);     // recursion
120                }
121            } else {
122                throw new RuntimeException("Unexpected \"" + childTypeUri + "\" value in ChildTopics: " + value);
123            }
124        }
125    }
126
127    // === Firing PRE_SEND events ===
128
129    private void firePreSend(DeepaMehtaObject object) {
130        if (object instanceof TopicType) {                  // Note: must take precedence over topic
131            em.fireEvent(CoreEvent.PRE_SEND_TOPIC_TYPE, object);
132        } else if (object instanceof AssociationType) {     // Note: must take precedence over topic
133            em.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION_TYPE, object);
134        } else if (object instanceof Topic) {
135            em.fireEvent(CoreEvent.PRE_SEND_TOPIC, object);
136        } else if (object instanceof Association) {
137            em.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION, object);
138        }
139    }
140
141    private void firePreSend(Iterable<DeepaMehtaObject> objects) {
142        for (DeepaMehtaObject object : objects) {
143            firePreSend(object);
144        }
145    }
146
147    private void firePreSend(Directives directives) {
148        for (Directives.Entry entry : directives) {
149            switch (entry.dir) {
150            case UPDATE_TOPIC:
151            case UPDATE_ASSOCIATION:
152            case UPDATE_TOPIC_TYPE:
153            case UPDATE_ASSOCIATION_TYPE:
154                firePreSend((DeepaMehtaObject) entry.arg);
155                break;
156            }
157        }
158    }
159
160    // === Helper ===
161
162    private boolean isIterable(ContainerResponse response, Class elementType) {
163        Type genericType = response.getEntityType();
164        if (genericType instanceof ParameterizedType) {
165            Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
166            Class<?> type = response.getEntity().getClass();
167            if (typeArgs.length == 1 && Iterable.class.isAssignableFrom(type) &&
168                                           elementType.isAssignableFrom((Class) typeArgs[0])) {
169                return true;
170            }
171        }
172        return false;
173    }
174
175    // ---
176
177    private boolean getIncludeChilds(ContainerRequest request) {
178        return getBooleanQueryParameter(request, "include_childs");
179    }
180
181    private boolean getIncludeAssocChilds(ContainerRequest request) {
182        return getBooleanQueryParameter(request, "include_assoc_childs");
183    }
184
185    // ---
186
187    private boolean getBooleanQueryParameter(ContainerRequest request, String param) {
188        return Boolean.parseBoolean(request.getQueryParameters().getFirst(param));
189    }
190}