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}