X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Fstreams%2Flisteners%2FListenerAdapter.java;h=0b2c1dd34d8811b2f6e79ba1b2754d29ff32ecd5;hb=4a84711256caebbfd4b59aacf99f59ced55d16db;hp=2289d2e4dd63945b83bea38672be8764abdca3f0;hpb=4c0c091813aea131d32dc70c5121a450eb9b7291;p=netconf.git diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java index 2289d2e4dd..0b2c1dd34d 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java @@ -7,45 +7,19 @@ */ package org.opendaylight.netconf.sal.streams.listeners; -import com.google.common.base.Charsets; import com.google.common.base.Preconditions; -import com.google.common.eventbus.AsyncEventBus; -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty.util.internal.ConcurrentSet; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Random; import java.util.Set; -import java.util.concurrent.Executors; -import java.util.regex.Pattern; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; +import org.json.JSONObject; +import org.json.XML; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener; import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; -import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -54,11 +28,8 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.slf4j.Logger; @@ -68,154 +39,94 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; /** - * {@link ListenerAdapter} is responsible to track events, which occurred by changing data in data source. + * {@link ListenerAdapter} is responsible to track events, which occurred by + * changing data in data source. */ -public class ListenerAdapter implements DOMDataChangeListener { +public class ListenerAdapter extends AbstractCommonSubscriber implements DOMDataChangeListener { private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class); - private static final DocumentBuilderFactory DBF = DocumentBuilderFactory.newInstance(); - private static final TransformerFactory FACTORY = TransformerFactory.newInstance(); - private static final Pattern RFC3339_PATTERN = Pattern.compile("(\\d\\d)(\\d\\d)$"); - - private final SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ"); private final YangInstanceIdentifier path; - private ListenerRegistration registration; private final String streamName; - private Set subscribers = new ConcurrentSet<>(); - private final EventBus eventBus; - private final EventBusChangeRecorder eventBusChangeRecorder; + private final NotificationOutputType outputType; + + private AsyncDataChangeEvent> change; /** - * Creates new {@link ListenerAdapter} listener specified by path and stream name. + * Creates new {@link ListenerAdapter} listener specified by path and stream + * name and register for subscribing * * @param path * Path to data in data store. * @param streamName * The name of the stream. + * @param outputType + * - type of output on notification (JSON, XML) */ - ListenerAdapter(final YangInstanceIdentifier path, final String streamName) { - Preconditions.checkNotNull(path); - Preconditions.checkArgument(streamName != null && !streamName.isEmpty()); - this.path = path; + ListenerAdapter(final YangInstanceIdentifier path, final String streamName, + final NotificationOutputType outputType) { + super(); + register(this); + setLocalNameOfPath(path.getLastPathArgument().getNodeType().getLocalName()); + + this.outputType = Preconditions.checkNotNull(outputType); + this.path = Preconditions.checkNotNull(path); + Preconditions.checkArgument((streamName != null) && !streamName.isEmpty()); this.streamName = streamName; - eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor()); - eventBusChangeRecorder = new EventBusChangeRecorder(); - eventBus.register(eventBusChangeRecorder); } @Override public void onDataChanged(final AsyncDataChangeEvent> change) { - if (!change.getCreatedData().isEmpty() || !change.getUpdatedData().isEmpty() - || !change.getRemovedPaths().isEmpty()) { - final String xml = prepareXmlFrom(change); - final Event event = new Event(EventType.NOTIFY); - event.setData(xml); - eventBus.post(event); + this.change = change; + final String xml = prepareXml(); + if (checkQueryParams(xml, this)) { + prepareAndPostData(xml); } } /** - * Tracks events of data change by customer. + * Gets the name of the stream. + * + * @return The name of the stream. */ - private final class EventBusChangeRecorder { - @Subscribe - public void recordCustomerChange(final Event event) { - if (event.getType() == EventType.REGISTER) { - final Channel subscriber = event.getSubscriber(); - if (!subscribers.contains(subscriber)) { - subscribers.add(subscriber); - } - } else if (event.getType() == EventType.DEREGISTER) { - subscribers.remove(event.getSubscriber()); - Notificator.removeListenerIfNoSubscriberExists(ListenerAdapter.this); - } else if (event.getType() == EventType.NOTIFY) { - for (final Channel subscriber : subscribers) { - if (subscriber.isActive()) { - LOG.debug("Data are sent to subscriber {}:", subscriber.remoteAddress()); - subscriber.writeAndFlush(new TextWebSocketFrame(event.getData())); - } else { - LOG.debug("Subscriber {} is removed - channel is not active yet.", subscriber.remoteAddress()); - subscribers.remove(subscriber); - } - } - } - } + @Override + public String getStreamName() { + return this.streamName; + } + + @Override + public String getOutputType() { + return this.outputType.getName(); } /** - * Represents event of specific {@link EventType} type, holds data and {@link Channel} subscriber. + * Get path pointed to data in data store. + * + * @return Path pointed to data in data store. */ - private final class Event { - private final EventType type; - private Channel subscriber; - private String data; - - /** - * Creates new event specified by {@link EventType} type. - * - * @param type - * EventType - */ - public Event(final EventType type) { - this.type = type; - } - - /** - * Gets the {@link Channel} subscriber. - * - * @return Channel - */ - public Channel getSubscriber() { - return subscriber; - } - - /** - * Sets subscriber for event. - * - * @param subscriber - * Channel - */ - public void setSubscriber(final Channel subscriber) { - this.subscriber = subscriber; - } - - /** - * Gets event String. - * - * @return String representation of event data. - */ - public String getData() { - return data; - } - - /** - * Sets event data. - * - * @param data String. - */ - public void setData(final String data) { - this.data = data; - } + public YangInstanceIdentifier getPath() { + return this.path; + } - /** - * Gets event type. - * - * @return The type of the event. - */ - public EventType getType() { - return type; + /** + * Prepare data of notification and data to client + * + * @param xml + */ + private void prepareAndPostData(final String xml) { + final Event event = new Event(EventType.NOTIFY); + if (this.outputType.equals(NotificationOutputType.JSON)) { + final JSONObject jsonObject = XML.toJSONObject(xml); + event.setData(jsonObject.toString()); + } else { + event.setData(xml); } + post(event); } /** - * Type of the event. + * Tracks events of data change by customer. */ - private enum EventType { - REGISTER, - DEREGISTER, - NOTIFY; - } /** * Prepare data in printable form and transform it to String. @@ -224,65 +135,19 @@ public class ListenerAdapter implements DOMDataChangeListener { * DataChangeEvent * @return Data in printable form. */ - private String prepareXmlFrom(final AsyncDataChangeEvent> change) { + private String prepareXml() { final SchemaContext schemaContext = ControllerContext.getInstance().getGlobalSchema(); - final DataSchemaContextTree dataContextTree = DataSchemaContextTree.from(schemaContext); + final DataSchemaContextTree dataContextTree = DataSchemaContextTree.from(schemaContext); final Document doc = createDocument(); - final Element notificationElement = doc.createElementNS("urn:ietf:params:xml:ns:netconf:notification:1.0", - "notification"); - doc.appendChild(notificationElement); - - final Element eventTimeElement = doc.createElement("eventTime"); - eventTimeElement.setTextContent(toRFC3339(new Date())); - notificationElement.appendChild(eventTimeElement); + final Element notificationElement = basePartDoc(doc); final Element dataChangedNotificationEventElement = doc.createElementNS( "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "data-changed-notification"); - addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, change, + + addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, this.change, schemaContext, dataContextTree); notificationElement.appendChild(dataChangedNotificationEventElement); - - try { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final Transformer transformer = FACTORY.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out, Charsets.UTF_8))); - final byte[] charData = out.toByteArray(); - return new String(charData, "UTF-8"); - } catch (TransformerException | UnsupportedEncodingException e) { - final String msg = "Error during transformation of Document into String"; - LOG.error(msg, e); - return msg; - } - } - - /** - * Formats data specified by RFC3339. - * - * @param d - * Date - * @return Data specified by RFC3339. - */ - private String toRFC3339(final Date d) { - return RFC3339_PATTERN.matcher(rfc3339.format(d)).replaceAll("$1:$2"); - } - - /** - * Creates {@link Document} document. - * @return {@link Document} document. - */ - private Document createDocument() { - final DocumentBuilder bob; - try { - bob = DBF.newDocumentBuilder(); - } catch (final ParserConfigurationException e) { - return null; - } - return bob.newDocument(); + return transformDoc(doc); } /** @@ -298,15 +163,13 @@ public class ListenerAdapter implements DOMDataChangeListener { private void addValuesToDataChangedNotificationEventElement(final Document doc, final Element dataChangedNotificationEventElement, final AsyncDataChangeEvent> change, - final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) { + final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) { addCreatedChangedValuesFromDataToElement(doc, change.getCreatedData().entrySet(), - dataChangedNotificationEventElement, - Operation.CREATED, schemaContext, dataSchemaContextTree); + dataChangedNotificationEventElement, Operation.CREATED, schemaContext, dataSchemaContextTree); addCreatedChangedValuesFromDataToElement(doc, change.getUpdatedData().entrySet(), - dataChangedNotificationEventElement, - Operation.UPDATED, schemaContext, dataSchemaContextTree); + dataChangedNotificationEventElement, Operation.UPDATED, schemaContext, dataSchemaContextTree); addValuesFromDataToElement(doc, change.getRemovedPaths(), dataChangedNotificationEventElement, Operation.DELETED); @@ -324,9 +187,9 @@ public class ListenerAdapter implements DOMDataChangeListener { * @param operation * {@link Operation} */ - private void addValuesFromDataToElement(final Document doc, final Set data, final Element element, - final Operation operation) { - if (data == null || data.isEmpty()) { + private void addValuesFromDataToElement(final Document doc, final Set data, + final Element element, final Operation operation) { + if ((data == null) || data.isEmpty()) { return; } for (final YangInstanceIdentifier path : data) { @@ -337,13 +200,14 @@ public class ListenerAdapter implements DOMDataChangeListener { } } - private void addCreatedChangedValuesFromDataToElement(final Document doc, final Set>> data, final Element element, final Operation operation, final SchemaContext - schemaContext, final DataSchemaContextTree dataSchemaContextTree) { - if (data == null || data.isEmpty()) { + private void addCreatedChangedValuesFromDataToElement(final Document doc, + final Set>> data, final Element element, + final Operation operation, final SchemaContext schemaContext, + final DataSchemaContextTree dataSchemaContextTree) { + if ((data == null) || data.isEmpty()) { return; } - for (Entry> entry : data) { + for (final Entry> entry : data) { if (!ControllerContext.getInstance().isNodeMixin(entry.getKey())) { final Node node = createCreatedChangedDataChangeEventElement(doc, entry, operation, schemaContext, dataSchemaContextTree); @@ -363,7 +227,8 @@ public class ListenerAdapter implements DOMDataChangeListener { * {@link Operation} * @return {@link Node} node represented by changed event element. */ - private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path, final Operation operation) { + private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path, + final Operation operation) { final Element dataChangeEventElement = doc.createElement("data-change-event"); final Element pathElement = doc.createElement("path"); addPathAsValueToElement(path, pathElement); @@ -376,9 +241,9 @@ public class ListenerAdapter implements DOMDataChangeListener { return dataChangeEventElement; } - private Node createCreatedChangedDataChangeEventElement(final Document doc, final Entry> entry, final Operation operation, final SchemaContext - schemaContext, final DataSchemaContextTree dataSchemaContextTree) { + private Node createCreatedChangedDataChangeEventElement(final Document doc, + final Entry> entry, final Operation operation, + final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) { final Element dataChangeEventElement = doc.createElement("data-change-event"); final Element pathElement = doc.createElement("path"); final YangInstanceIdentifier path = entry.getKey(); @@ -390,61 +255,27 @@ public class ListenerAdapter implements DOMDataChangeListener { dataChangeEventElement.appendChild(operationElement); try { - final DOMResult domResult = writeNormalizedNode(entry.getValue(), path, - schemaContext, dataSchemaContextTree); + SchemaPath nodePath; + final NormalizedNode normalized = entry.getValue(); + if ((normalized instanceof MapEntryNode) || (normalized instanceof UnkeyedListEntryNode)) { + nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath(); + } else { + nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath().getParent(); + } + final DOMResult domResult = writeNormalizedNode(normalized, schemaContext, nodePath); final Node result = doc.importNode(domResult.getNode().getFirstChild(), true); final Element dataElement = doc.createElement("data"); dataElement.appendChild(result); dataChangeEventElement.appendChild(dataElement); - } catch (IOException e) { + } catch (final IOException e) { LOG.error("Error in writer ", e); - } catch (XMLStreamException e) { + } catch (final XMLStreamException e) { LOG.error("Error processing stream", e); } return dataChangeEventElement; } - private static DOMResult writeNormalizedNode(final NormalizedNode normalized, final - YangInstanceIdentifier path, final SchemaContext context, final DataSchemaContextTree dataSchemaContextTree) throws - IOException, XMLStreamException { - final XMLOutputFactory XML_FACTORY = XMLOutputFactory.newFactory(); - final Document doc = XmlDocumentUtils.getDocument(); - final DOMResult result = new DOMResult(doc); - NormalizedNodeWriter normalizedNodeWriter = null; - NormalizedNodeStreamWriter normalizedNodeStreamWriter = null; - XMLStreamWriter writer = null; - final SchemaPath nodePath; - - if (normalized instanceof MapEntryNode || normalized instanceof UnkeyedListEntryNode) { - nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath(); - } else { - nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath().getParent(); - } - - try { - writer = XML_FACTORY.createXMLStreamWriter(result); - normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, context, nodePath); - normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter); - - normalizedNodeWriter.write(normalized); - - normalizedNodeWriter.flush(); - } finally { - if (normalizedNodeWriter != null) { - normalizedNodeWriter.close(); - } - if (normalizedNodeStreamWriter != null) { - normalizedNodeStreamWriter.close(); - } - if (writer != null) { - writer.close(); - } - } - - return result; - } - /** * Adds path as value to element. * @@ -453,25 +284,23 @@ public class ListenerAdapter implements DOMDataChangeListener { * @param element * {@link Element} */ + @SuppressWarnings("rawtypes") private void addPathAsValueToElement(final YangInstanceIdentifier path, final Element element) { - // Map< key = namespace, value = prefix> - final Map prefixes = new HashMap<>(); final YangInstanceIdentifier normalizedPath = ControllerContext.getInstance().toXpathRepresentation(path); final StringBuilder textContent = new StringBuilder(); - // FIXME: BUG-1281: this is duplicated code from yangtools (BUG-1275) for (final PathArgument pathArgument : normalizedPath.getPathArguments()) { if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) { continue; } textContent.append("/"); - writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType(), prefixes); + writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType()); if (pathArgument instanceof NodeIdentifierWithPredicates) { final Map predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues(); for (final QName keyValue : predicates.keySet()) { final String predicateValue = String.valueOf(predicates.get(keyValue)); textContent.append("["); - writeIdentifierWithNamespacePrefix(element, textContent, keyValue, prefixes); + writeIdentifierWithNamespacePrefix(element, textContent, keyValue); textContent.append("='"); textContent.append(predicateValue); textContent.append("'"); @@ -496,151 +325,23 @@ public class ListenerAdapter implements DOMDataChangeListener { * StringBuilder * @param qName * QName - * @param prefixes - * Map of namespaces and prefixes. */ private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent, - final QName qName, final Map prefixes) { - final String namespace = qName.getNamespace().toString(); - String prefix = prefixes.get(namespace); - if (prefix == null) { - prefix = generateNewPrefix(prefixes.values()); - } - - element.setAttribute("xmlns:" + prefix, namespace); - textContent.append(prefix); - prefixes.put(namespace, prefix); + final QName qName) { + final Module module = ControllerContext.getInstance().getGlobalSchema() + .findModuleByNamespaceAndRevision(qName.getNamespace(), qName.getRevision()); + textContent.append(module.getName()); textContent.append(":"); textContent.append(qName.getLocalName()); } /** - * Generates new prefix which consists of four random characters . - * - * @param prefixes - * Collection of prefixes. - * @return New prefix which consists of four random characters . - */ - private static String generateNewPrefix(final Collection prefixes) { - StringBuilder result = null; - final Random random = new Random(); - do { - result = new StringBuilder(); - for (int i = 0; i < 4; i++) { - final int randomNumber = 0x61 + (Math.abs(random.nextInt()) % 26); - result.append(Character.toChars(randomNumber)); - } - } while (prefixes.contains(result.toString())); - - return result.toString(); - } - - /** - * Gets path pointed to data in data store. - * - * @return Path pointed to data in data store. - */ - public YangInstanceIdentifier getPath() { - return path; - } - - /** - * Sets {@link ListenerRegistration} registration. - * - * @param registration DOMDataChangeListener registration - */ - public void setRegistration(final ListenerRegistration registration) { - this.registration = registration; - } - - /** - * Gets the name of the stream. - * - * @return The name of the stream. - */ - public String getStreamName() { - return streamName; - } - - /** - * Removes all subscribers and unregisters event bus change recorder form event bus. - */ - public void close() throws Exception { - subscribers = new ConcurrentSet<>(); - registration.close(); - registration = null; - eventBus.unregister(eventBusChangeRecorder); - } - - /** - * Checks if {@link ListenerRegistration} registration exist. - * - * @return True if exist, false otherwise. - */ - public boolean isListening() { - return registration == null ? false : true; - } - - /** - * Creates event of type {@link EventType#REGISTER}, set {@link Channel} subscriber to the event and post event into - * event bus. - * - * @param subscriber - * Channel - */ - public void addSubscriber(final Channel subscriber) { - if (!subscriber.isActive()) { - LOG.debug("Channel is not active between websocket server and subscriber {}" + subscriber.remoteAddress()); - } - final Event event = new Event(EventType.REGISTER); - event.setSubscriber(subscriber); - eventBus.post(event); - } - - /** - * Creates event of type {@link EventType#DEREGISTER}, sets {@link Channel} subscriber to the event and posts event - * into event bus. - * - * @param subscriber - */ - public void removeSubscriber(final Channel subscriber) { - LOG.debug("Subscriber {} is removed.", subscriber.remoteAddress()); - final Event event = new Event(EventType.DEREGISTER); - event.setSubscriber(subscriber); - eventBus.post(event); - } - - /** - * Checks if exists at least one {@link Channel} subscriber. - * - * @return True if exist at least one {@link Channel} subscriber, false otherwise. - */ - public boolean hasSubscribers() { - return !subscribers.isEmpty(); - } - - /** - * Consists of two types {@link Store#CONFIG} and {@link Store#OPERATION}. - */ - private static enum Store { - CONFIG("config"), - OPERATION("operation"); - - private final String value; - - private Store(final String value) { - this.value = value; - } - } - - /** - * Consists of three types {@link Operation#CREATED}, {@link Operation#UPDATED} and {@link Operation#DELETED}. + * Consists of three types {@link Operation#CREATED}, + * {@link Operation#UPDATED} and {@link Operation#DELETED}. */ private static enum Operation { - CREATED("created"), - UPDATED("updated"), - DELETED("deleted"); + CREATED("created"), UPDATED("updated"), DELETED("deleted"); private final String value; @@ -648,5 +349,4 @@ public class ListenerAdapter implements DOMDataChangeListener { this.value = value; } } - }