Project

General

Profile

1
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
	private Collection<ResourceStateSubscriptionRegistry> registries;
42

    
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
		log.debug("resource created: " + registries);
51
		for (ResourceStateSubscriptionRegistry registry : registries) {
52
			for (ResourceStateSubscription sub : registry.listMatchingSubscriptions(ResourceStateSubscription.PREFIX_CREATE, newResource
53
					.getResourceType(), newResource.getResourceId()))
54
				if (matchPath(newResource, sub.getXpath()))
55
					send(sub, newResource, ResourceStateSubscription.PREFIX_CREATE);
56
		}
57
	}
58

    
59
	/**
60
	 * helper method. sends a notification for a given prefix.
61
	 *
62
	 * @param sub
63
	 *            subscription
64
	 * @param resource
65
	 *            resource
66
	 * @param prefix
67
	 *            prefix
68
	 */
69
	private void send(final ResourceStateSubscription sub, final OpaqueResource resource, final String prefix) {
70
		log.debug("RESOURCE " + resource);
71
		log.debug("id: " + resource.getResourceId());
72
		log.debug("dom: " + resource.asDom());
73

    
74
		final StringBuffer topicBuffer = new StringBuffer();
75

    
76
		if (sub.getPrefix() == null && "*".equals(sub.getPrefix()))
77
			topicBuffer.append(prefix);
78
		else
79
			topicBuffer.append(sub.getPrefix());
80

    
81
		topicBuffer.append('.');
82
		topicBuffer.append(sub.getType());
83
		topicBuffer.append('.');
84
		topicBuffer.append(sub.getResourceId());
85
		if (sub.getXpath() != null && !sub.getXpath().isEmpty()) {
86
			topicBuffer.append(sub.getXpath().replace('/', '.'));
87
		}
88

    
89
		getSender().send(sub.getSubscriberAsEpr(), new NotificationMessage(sub.getSubscriptionId(), topicBuffer.toString(), resource.getResourceId(), // NOPMD
90
				resource.asString()));
91
	}
92

    
93
	/**
94
	 * {@inheritDoc}
95
	 *
96
	 * @see eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector#resourceDeleted(java.lang.Object)
97
	 */
98
	@Override
99
	public void resourceDeleted(final OpaqueResource oldResource) {
100
		log.debug("resource deleted: " + registries);
101
		for (ResourceStateSubscriptionRegistry registry : registries) {
102
			for (ResourceStateSubscription sub : registry.listMatchingSubscriptions(ResourceStateSubscription.PREFIX_DELETE, oldResource
103
					.getResourceType(), oldResource.getResourceId()))
104
				if (matchPath(oldResource, sub.getXpath()))
105
					send(sub, oldResource, ResourceStateSubscription.PREFIX_DELETE);
106
		}
107
	}
108

    
109
	/**
110
	 * {@inheritDoc}
111
	 *
112
	 * @see eu.dnetlib.enabling.is.sn.resourcestate.ResourceStateNotificationDetector#resourceUpdated(java.lang.Object,
113
	 *      java.lang.Object)
114
	 */
115
	@Override
116
	public void resourceUpdated(final OpaqueResource oldResource, final OpaqueResource newResource) {
117
		log.debug("resource updated: " + registries);
118
		for (ResourceStateSubscriptionRegistry registry : registries) {
119
			for (ResourceStateSubscription sub : registry.listMatchingSubscriptions(ResourceStateSubscription.PREFIX_UPDATE, oldResource
120
					.getResourceType(), oldResource.getResourceId()))
121
				if (comparePath(oldResource, newResource, sub.getXpath())) {
122
					log.debug("updated, sending: " + newResource.asString());
123
					send(sub, newResource, ResourceStateSubscription.PREFIX_UPDATE);
124
				}
125
		}
126
	}
127

    
128
	/**
129
	 * check if an xpath matches a given resource.
130
	 *
131
	 * @param resource
132
	 *            resource
133
	 * @param xpath
134
	 *            xpath
135
	 * @return true if the resource has some value for the given path
136
	 */
137
	private boolean matchPath(final OpaqueResource resource, final String xpath) {
138
		// by convention empty xpath matches any document
139
		if (xpath == null || xpath.isEmpty())
140
			return true;
141

    
142
		final XPath xpa = XPathFactory.newInstance().newXPath();
143
		try {
144
			return !xpa.evaluate(xpath, resource.asDom()).isEmpty();
145
		} catch (XPathExpressionException e) {
146
			log.warn("wrong xpath expression, notification possibly missed", e);
147
		}
148
		return false;
149
	}
150

    
151
	/**
152
	 * compare the content of two profiles for the same xpath. Return true if some change has been made so that the
153
	 * notification can be delivered.
154
	 *
155
	 * @param oldResource
156
	 *            old version
157
	 * @param newResource
158
	 *            new version
159
	 * @param xpath
160
	 *            XPath
161
	 * @return true if the two documents differ under a given path
162
	 */
163
	private boolean comparePath(final OpaqueResource oldResource, final OpaqueResource newResource, final String xpath) {
164
		// by convention empty xpath matches any document
165
		if (oldResource == null || newResource == null || xpath == null || xpath.isEmpty())
166
			return true;
167

    
168
		final XPath xpa = XPathFactory.newInstance().newXPath();
169
		try {
170
			final Transformer transformer = TransformerFactory.newInstance().newTransformer();
171

    
172
			final Node left = (Node) xpa.evaluate(xpath, oldResource.asDom(), XPathConstants.NODE);
173
			final Node right = (Node) xpa.evaluate(xpath, newResource.asDom(), XPathConstants.NODE);
174
			
175
			if (left==null || right==null) {
176
				if (left != null) {
177
					return true;
178
				}
179
				else {
180
					if (right != null) {
181
						return true;
182
					}
183
					else {
184
						return false;
185
					}
186
				}
187
			}
188
			
189
			final StringWriter leftWriter = new StringWriter();
190
			final StringWriter rightWriter = new StringWriter();
191

    
192
			transformer.transform(new DOMSource(left), new StreamResult(leftWriter));
193
			transformer.transform(new DOMSource(right), new StreamResult(rightWriter));
194
			
195
			return !leftWriter.toString().equals(rightWriter.toString());
196
		} catch (XPathExpressionException e) {
197
			log.warn("wrong xpath expression, notification possibly missed", e);
198
		} catch (TransformerException e) {
199
			log.warn("serialization problem", e);
200
		}
201

    
202
		return false;
203
	}
204

    
205
	public Collection<ResourceStateSubscriptionRegistry> getRegistries() {
206
		return registries;
207
	}
208

    
209
	public void setRegistries(final Collection<ResourceStateSubscriptionRegistry> registries) {
210
		this.registries = registries;
211
	}
212

    
213
}
(11-11/23)