001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationType;
005    import de.deepamehta.core.Topic;
006    import de.deepamehta.core.TopicType;
007    import de.deepamehta.core.service.ClientState;
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                if (entity != null) {
044                    ClientState clientState = clientState(request);
045                    if (entity instanceof TopicType) {                      // Note: must take precedence over topic
046                        firePreSend((TopicType) entity, clientState);
047                    } else if (entity instanceof AssociationType) {
048                        firePreSend((AssociationType) entity, clientState); // Note: must take precedence over topic
049                    } else if (entity instanceof Topic) {
050                        firePreSend((Topic) entity, clientState);
051                    } else if (entity instanceof Association) {
052                        firePreSend((Association) entity, clientState);
053                    } else if (entity instanceof Directives) {
054                        // Note: some plugins rely on the PRE_SEND event in order to enrich updated objects, others don't.
055                        // E.g. the Access Control plugin must enrich updated objects with permission information.
056                        firePreSend((Directives) entity, clientState);
057                    } else if (isIterable(response, TopicType.class)) {
058                        firePreSendTopicTypes((Iterable<TopicType>) entity, clientState);
059                    } else if (isIterable(response, AssociationType.class)) {
060                        firePreSendAssociationTypes((Iterable<AssociationType>) entity, clientState);
061                    } else if (isIterable(response, Topic.class)) {
062                        firePreSendTopics((Iterable<Topic>) entity, clientState);
063                    }
064                }
065                return response;
066            } catch (Exception e) {
067                throw new RuntimeException("Jersey response filtering failed", e);
068            }
069        }
070    
071        // ------------------------------------------------------------------------------------------------- Private Methods
072    
073        private void firePreSend(Topic topic, ClientState clientState) {
074            dms.fireEvent(CoreEvent.PRE_SEND_TOPIC, topic, clientState);
075        }
076    
077        private void firePreSend(Association assoc, ClientState clientState) {
078            dms.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION, assoc, clientState);
079        }
080    
081        private void firePreSend(TopicType topicType, ClientState clientState) {
082            dms.fireEvent(CoreEvent.PRE_SEND_TOPIC_TYPE, topicType, clientState);
083        }
084    
085        private void firePreSend(AssociationType assocType, ClientState clientState) {
086            dms.fireEvent(CoreEvent.PRE_SEND_ASSOCIATION_TYPE, assocType, clientState);
087        }
088    
089        private void firePreSend(Directives directives, ClientState clientState) {
090            for (Directives.Entry entry : directives) {
091                switch (entry.dir) {
092                case UPDATE_TOPIC:
093                    firePreSend((Topic) entry.arg, clientState);
094                    break;
095                case UPDATE_ASSOCIATION:
096                    firePreSend((Association) entry.arg, clientState);
097                    break;
098                case UPDATE_TOPIC_TYPE:
099                    firePreSend((TopicType) entry.arg, clientState);
100                    break;
101                case UPDATE_ASSOCIATION_TYPE:
102                    firePreSend((AssociationType) entry.arg, clientState);
103                    break;
104                }
105            }
106        }
107    
108        private void firePreSendTopics(Iterable<Topic> topics, ClientState clientState) {
109            for (Topic topic : topics) {
110                firePreSend(topic, clientState);
111            }
112        }
113    
114        private void firePreSendTopicTypes(Iterable<TopicType> topicTypes, ClientState clientState) {
115            for (TopicType topicType : topicTypes) {
116                firePreSend(topicType, clientState);
117            }
118        }
119    
120        private void firePreSendAssociationTypes(Iterable<AssociationType> assocTypes, ClientState clientState) {
121            for (AssociationType assocType : assocTypes) {
122                firePreSend(assocType, clientState);
123            }
124        }
125    
126        // ---
127    
128        private boolean isIterable(ContainerResponse response, Class elementType) {
129            Type genericType = response.getEntityType();
130            if (genericType instanceof ParameterizedType) {
131                Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
132                Class<?> type = response.getEntity().getClass();
133                if (typeArgs.length == 1 && Iterable.class.isAssignableFrom(type) &&
134                                               elementType.isAssignableFrom((Class) typeArgs[0])) {
135                    return true;
136                }
137            }
138            return false;
139        }
140    
141        private ClientState clientState(ContainerRequest request) {
142            List<String> cookies = request.getRequestHeader("Cookie");
143            if (cookies == null) {
144                return new ClientState(null);
145            }
146            // ### FIXME: does this happen?
147            if (cookies.size() > 1) {
148                throw new RuntimeException("Request contains more than one Cookie header");
149            }
150            //
151            return new ClientState(cookies.get(0));
152        }
153    }