Project

General

Profile

1 26600 sandro.lab
package eu.dnetlib.enabling.is.sn;
2
3
import java.io.StringWriter;
4
import java.util.Collection;
5
6
import javax.xml.transform.Transformer;
7
import javax.xml.transform.TransformerException;
8
import javax.xml.transform.TransformerFactory;
9
import javax.xml.transform.dom.DOMSource;
10
import javax.xml.transform.stream.StreamResult;
11
import javax.xml.xpath.XPath;
12
import javax.xml.xpath.XPathConstants;
13
import javax.xml.xpath.XPathExpressionException;
14
import javax.xml.xpath.XPathFactory;
15
16
import org.apache.commons.logging.Log;
17
import org.apache.commons.logging.LogFactory;
18
import org.w3c.dom.Node;
19
20
import eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector;
21
import eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateSubscription;
22
import eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateSubscriptionRegistry;
23
import eu.dnetlib.enabling.tools.OpaqueResource;
24
25
/**
26
 * This notification detector uses a local xmldb trigger as a source of events.
27
 *
28
 * @author marko
29
 *
30
 */
31
public class NotificationDetectorImpl extends AbstractNotificationDetector implements ResourceStateNotificationDetector<OpaqueResource> {
32
33
	/**
34
	 * logger.
35
	 */
36
	private static final Log log = LogFactory.getLog(NotificationDetectorImpl.class); // NOPMD by marko on 11/24/08 5:02 PM
37
38
	/**
39
	 * subscription registries to lookup.
40
	 */
41 43289 claudio.at
	private SubscriptionRegistry registry;
42 26600 sandro.lab
43
	/**
44
	 * {@inheritDoc}
45
	 *
46
	 * @see eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector#resourceCreated(java.lang.Object)
47
	 */
48
	@Override
49
	public void resourceCreated(final OpaqueResource newResource) {
50 43289 claudio.at
		log.debug("resource created: " + getRegistry());
51
52
		final Collection<ResourceStateSubscription> subs =
53
				getRegistry().listMatchingSubscriptions(ResourceStateSubscription.PREFIX_CREATE, newResource.getResourceType(), newResource.getResourceId());
54
		for (ResourceStateSubscription sub : subs) {
55
			if (matchPath(newResource, sub.getXpath())) {
56
				send(sub, newResource, ResourceStateSubscription.PREFIX_CREATE);
57
			}
58 26600 sandro.lab
		}
59
	}
60
61
	/**
62
	 * helper method. sends a notification for a given prefix.
63
	 *
64
	 * @param sub
65
	 *            subscription
66
	 * @param resource
67
	 *            resource
68
	 * @param prefix
69
	 *            prefix
70
	 */
71
	private void send(final ResourceStateSubscription sub, final OpaqueResource resource, final String prefix) {
72
		log.debug("RESOURCE " + resource);
73
		log.debug("id: " + resource.getResourceId());
74
		log.debug("dom: " + resource.asDom());
75
76
		final StringBuffer topicBuffer = new StringBuffer();
77
78
		if (sub.getPrefix() == null && "*".equals(sub.getPrefix()))
79
			topicBuffer.append(prefix);
80
		else
81
			topicBuffer.append(sub.getPrefix());
82
83
		topicBuffer.append('.');
84
		topicBuffer.append(sub.getType());
85
		topicBuffer.append('.');
86
		topicBuffer.append(sub.getResourceId());
87
		if (sub.getXpath() != null && !sub.getXpath().isEmpty()) {
88
			topicBuffer.append(sub.getXpath().replace('/', '.'));
89
		}
90
91
		getSender().send(sub.getSubscriberAsEpr(), new NotificationMessage(sub.getSubscriptionId(), topicBuffer.toString(), resource.getResourceId(), // NOPMD
92
				resource.asString()));
93
	}
94
95
	/**
96
	 * {@inheritDoc}
97
	 *
98
	 * @see eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector#resourceDeleted(java.lang.Object)
99
	 */
100
	@Override
101
	public void resourceDeleted(final OpaqueResource oldResource) {
102 43289 claudio.at
		log.debug("resource deleted: " + getRegistry());
103
104
		final Collection<ResourceStateSubscription> subs =
105
				registry.listMatchingSubscriptions(ResourceStateSubscription.PREFIX_DELETE, oldResource.getResourceType(), oldResource.getResourceId());
106
		for (ResourceStateSubscription sub : subs) {
107
			if (matchPath(oldResource, sub.getXpath())) {
108
				send(sub, oldResource, ResourceStateSubscription.PREFIX_DELETE);
109
			}
110 26600 sandro.lab
		}
111
	}
112
113
	/**
114
	 * {@inheritDoc}
115
	 *
116
	 * @see eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector#resourceUpdated(java.lang.Object,
117
	 *      java.lang.Object)
118
	 */
119
	@Override
120
	public void resourceUpdated(final OpaqueResource oldResource, final OpaqueResource newResource) {
121 43289 claudio.at
		log.debug("resource updated: " + getRegistry());
122
123
		final Collection<ResourceStateSubscription> subs =
124
				registry.listMatchingSubscriptions(ResourceStateSubscription.PREFIX_UPDATE, oldResource.getResourceType(), oldResource.getResourceId());
125
		for (ResourceStateSubscription sub : subs) {
126
			if (comparePath(oldResource, newResource, sub.getXpath())) {
127
				log.debug("updated, sending: " + newResource.asString());
128
				send(sub, newResource, ResourceStateSubscription.PREFIX_UPDATE);
129
			}
130 26600 sandro.lab
		}
131
	}
132
133
	/**
134
	 * check if an xpath matches a given resource.
135
	 *
136
	 * @param resource
137
	 *            resource
138
	 * @param xpath
139
	 *            xpath
140
	 * @return true if the resource has some value for the given path
141
	 */
142
	private boolean matchPath(final OpaqueResource resource, final String xpath) {
143
		// by convention empty xpath matches any document
144
		if (xpath == null || xpath.isEmpty())
145
			return true;
146
147
		final XPath xpa = XPathFactory.newInstance().newXPath();
148
		try {
149
			return !xpa.evaluate(xpath, resource.asDom()).isEmpty();
150
		} catch (XPathExpressionException e) {
151
			log.warn("wrong xpath expression, notification possibly missed", e);
152
		}
153
		return false;
154
	}
155
156
	/**
157
	 * compare the content of two profiles for the same xpath. Return true if some change has been made so that the
158
	 * notification can be delivered.
159
	 *
160
	 * @param oldResource
161
	 *            old version
162
	 * @param newResource
163
	 *            new version
164
	 * @param xpath
165
	 *            XPath
166
	 * @return true if the two documents differ under a given path
167
	 */
168
	private boolean comparePath(final OpaqueResource oldResource, final OpaqueResource newResource, final String xpath) {
169
		// by convention empty xpath matches any document
170
		if (oldResource == null || newResource == null || xpath == null || xpath.isEmpty())
171
			return true;
172
173
		final XPath xpa = XPathFactory.newInstance().newXPath();
174
		try {
175
			final Transformer transformer = TransformerFactory.newInstance().newTransformer();
176
177
			final Node left = (Node) xpa.evaluate(xpath, oldResource.asDom(), XPathConstants.NODE);
178
			final Node right = (Node) xpa.evaluate(xpath, newResource.asDom(), XPathConstants.NODE);
179
180
			if (left==null || right==null) {
181
				if (left != null) {
182
					return true;
183
				}
184
				else {
185
					if (right != null) {
186
						return true;
187
					}
188
					else {
189
						return false;
190
					}
191
				}
192
			}
193
194
			final StringWriter leftWriter = new StringWriter();
195
			final StringWriter rightWriter = new StringWriter();
196
197
			transformer.transform(new DOMSource(left), new StreamResult(leftWriter));
198
			transformer.transform(new DOMSource(right), new StreamResult(rightWriter));
199
200
			return !leftWriter.toString().equals(rightWriter.toString());
201
		} catch (XPathExpressionException e) {
202
			log.warn("wrong xpath expression, notification possibly missed", e);
203
		} catch (TransformerException e) {
204
			log.warn("serialization problem", e);
205
		}
206
207
		return false;
208
	}
209
210 43289 claudio.at
	public SubscriptionRegistry getRegistry() {
211
		return registry;
212 26600 sandro.lab
	}
213
214 43289 claudio.at
	public void setRegistry(final SubscriptionRegistry registry) {
215
		this.registry = registry;
216 26600 sandro.lab
	}
217
}