summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight
diff options
context:
space:
mode:
Diffstat (limited to 'netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight')
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYangBodyWriter.java57
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYinBodyWriter.java51
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalService.java30
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalServiceImpl.java92
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java17
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java15
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java18
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java435
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java9
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java63
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java305
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java174
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java486
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java200
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java181
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java130
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java143
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java72
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java128
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java51
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java430
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java62
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java29
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java49
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java200
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java313
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java8
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java99
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/RestConfConfig.java30
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java90
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java75
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java1277
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java1006
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java16
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java526
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java81
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java291
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java143
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java51
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java69
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java380
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java1546
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java111
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java301
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java81
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java69
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java38
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java68
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java16
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java67
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java13
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java8
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/web/WebInitializer.java60
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractCommonSubscriber.java142
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractNotificationsData.java153
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractQueryParams.java161
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/BaseListenerInterface.java47
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Event.java77
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventBusChangeRecorder.java53
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventType.java15
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java425
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java181
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java230
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServer.java152
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerHandler.java185
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerInitializer.java31
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilder.java20
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilder.java29
-rw-r--r--netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilder.java42
73 files changed, 12473 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYangBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYangBodyWriter.java
new file mode 100644
index 0000000..c377650
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYangBodyWriter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.md.sal.rest.schema;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.concurrent.ExecutionException;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+
+@Provider
+@Produces({ YangConstants.RFC6020_YANG_MEDIA_TYPE })
+public class SchemaExportContentYangBodyWriter implements MessageBodyWriter<SchemaExportContext> {
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return type.equals(SchemaExportContext.class);
+ }
+
+ @Override
+ public long getSize(final SchemaExportContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final SchemaExportContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException,
+ WebApplicationException {
+ final RevisionSourceIdentifier sourceId = RevisionSourceIdentifier.create(context.getModule().getName(),
+ context.getModule().getQNameModule().getRevision());
+ final YangTextSchemaSource yangTextSchemaSource;
+ try {
+ yangTextSchemaSource = context.getSourceProvider().getSource(sourceId).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new WebApplicationException("Unable to retrieve source from SourceProvider.", e);
+ }
+ yangTextSchemaSource.copyTo(entityStream);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYinBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYinBodyWriter.java
new file mode 100644
index 0000000..9f4f8be
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaExportContentYinBodyWriter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.md.sal.rest.schema;
+
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.XMLStreamException;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+import org.opendaylight.yangtools.yang.model.export.YinExportUtils;
+
+@Provider
+@Produces({ YangConstants.RFC6020_YIN_MEDIA_TYPE })
+public class SchemaExportContentYinBodyWriter implements MessageBodyWriter<SchemaExportContext> {
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return type.equals(SchemaExportContext.class);
+ }
+
+ @Override
+ public long getSize(final SchemaExportContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final SchemaExportContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws
+ WebApplicationException {
+ try {
+ YinExportUtils.writeModuleAsYinText(context.getModule(), entityStream);
+ } catch (final XMLStreamException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalService.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalService.java
new file mode 100644
index 0000000..a20d041
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.md.sal.rest.schema;
+
+import com.google.common.annotations.Beta;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+
+/**
+ * Retrieval of the YANG modules which server supports.
+ *
+ * @deprecated do not use this api. It is replaced by RestconfSchemaService
+ */
+@Deprecated
+@Beta
+public interface SchemaRetrievalService {
+ @GET
+ @Produces({YangConstants.RFC6020_YIN_MEDIA_TYPE, YangConstants.RFC6020_YANG_MEDIA_TYPE})
+ @Path("/modules/module/{identifier:.+}/schema")
+ SchemaExportContext getSchema(@PathParam("identifier") String mountAndModuleId);
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalServiceImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalServiceImpl.java
new file mode 100644
index 0000000..ad23992
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/md/sal/rest/schema/SchemaRetrievalServiceImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.md.sal.rest.schema;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.time.format.DateTimeParseException;
+import java.util.Iterator;
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemaRetrievalServiceImpl implements SchemaRetrievalService {
+
+ private final ControllerContext salContext;
+
+ private static final Splitter SLASH_SPLITTER = Splitter.on("/");
+ private static final String MOUNT_ARG = ControllerContext.MOUNT;
+
+ public SchemaRetrievalServiceImpl(final ControllerContext controllerContext) {
+ salContext = controllerContext;
+ }
+
+
+ @Override
+ public SchemaExportContext getSchema(final String mountAndModule) {
+ final SchemaContext schemaContext;
+ final Iterable<String> pathComponents = SLASH_SPLITTER.split(mountAndModule);
+ final Iterator<String> componentIter = pathComponents.iterator();
+ if (!Iterables.contains(pathComponents, MOUNT_ARG)) {
+ schemaContext = salContext.getGlobalSchema();
+ } else {
+ final StringBuilder pathBuilder = new StringBuilder();
+ while (componentIter.hasNext()) {
+ final String current = componentIter.next();
+ // It is argument, not last element.
+ if (pathBuilder.length() != 0) {
+ pathBuilder.append("/");
+ }
+ pathBuilder.append(current);
+ if (MOUNT_ARG.equals(current)) {
+ // We stop right at mountpoint, last two arguments should
+ // be module name and revision
+ break;
+ }
+ }
+ schemaContext = getMountSchemaContext(pathBuilder.toString());
+
+ }
+
+ RestconfDocumentedException.throwIf(!componentIter.hasNext(), "Module name must be supplied.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ final String moduleName = componentIter.next();
+ RestconfDocumentedException.throwIf(!componentIter.hasNext(), "Revision date must be supplied.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ final String revisionString = componentIter.next();
+ return getExportUsingNameAndRevision(schemaContext, moduleName, revisionString,
+ salContext.getYangTextSourceProvider());
+ }
+
+ private static SchemaExportContext getExportUsingNameAndRevision(final SchemaContext schemaContext,
+ final String moduleName, final String revisionStr,
+ final DOMYangTextSourceProvider yangTextSourceProvider) {
+ try {
+ final Module module = schemaContext.findModule(moduleName, Revision.of(revisionStr)).orElse(null);
+ return new SchemaExportContext(
+ schemaContext, RestconfValidationUtils.checkNotNullDocumented(module, moduleName),
+ yangTextSourceProvider);
+ } catch (final DateTimeParseException e) {
+ throw new RestconfDocumentedException("Supplied revision is not in expected date format YYYY-mm-dd", e);
+ }
+ }
+
+ private SchemaContext getMountSchemaContext(final String identifier) {
+ final InstanceIdentifierContext mountContext = salContext.toMountPointIdentifier(identifier);
+ return mountContext.getSchemaContext();
+ }
+}
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java
new file mode 100644
index 0000000..67c8729
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/Draft02.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.api;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+/**
+ * Base Draft for Restconf project.
+ *
+ * @deprecated Do not use old implementation of restconf draft. It will be replaced by Rfc8040.
+ */
+@Deprecated
+public class Draft02 {
+ public interface MediaTypes {
+ String API = "application/yang.api";
+ String DATASTORE = "application/yang.datastore";
+ String DATA = "application/yang.data";
+ String OPERATION = "application/yang.operation";
+ String PATCH = "application/yang.patch";
+ String PATCH_STATUS = "application/yang.patch-status";
+ String STREAM = "application/yang.stream";
+ }
+
+ public interface RestConfModule {
+ String REVISION = "2013-10-19";
+
+ String NAME = "ietf-restconf";
+
+ String NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf";
+
+ String RESTCONF_GROUPING_SCHEMA_NODE = "restconf";
+
+ String RESTCONF_CONTAINER_SCHEMA_NODE = "restconf";
+
+ String MODULES_CONTAINER_SCHEMA_NODE = "modules";
+
+ String MODULE_LIST_SCHEMA_NODE = "module";
+
+ String STREAMS_CONTAINER_SCHEMA_NODE = "streams";
+
+ String STREAM_LIST_SCHEMA_NODE = "stream";
+
+ String ERROR_LIST_SCHEMA_NODE = "error";
+
+ QName IETF_RESTCONF_QNAME = QName.create(Draft02.RestConfModule.NAMESPACE, Draft02.RestConfModule.REVISION,
+ Draft02.RestConfModule.NAME);
+
+ QName ERRORS_QNAME = QName.create(IETF_RESTCONF_QNAME, "errors");
+
+ QName ERROR_LIST_QNAME = QName.create(IETF_RESTCONF_QNAME, ERROR_LIST_SCHEMA_NODE);
+
+ QName ERROR_TYPE_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-type");
+
+ QName ERROR_TAG_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-tag");
+
+ QName ERROR_APP_TAG_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-app-tag");
+
+ QName ERROR_MESSAGE_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-message");
+
+ QName ERROR_INFO_QNAME = QName.create(IETF_RESTCONF_QNAME, "error-info");
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java
new file mode 100644
index 0000000..3200958
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestConnector.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.api;
+
+/*
+ * This is a simple dummy interface to allow us to create instances of RestconfProvider
+ * via the config subsystem.
+ */
+public interface RestConnector {
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java
new file mode 100644
index 0000000..6a4ba93
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfConstants.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.api;
+
+public interface RestconfConstants {
+
+ String IDENTIFIER = "identifier";
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java
new file mode 100644
index 0000000..eb160bd
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfNormalizedNodeWriter.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.api;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public interface RestconfNormalizedNodeWriter extends Flushable, Closeable {
+
+ RestconfNormalizedNodeWriter write(NormalizedNode node) throws IOException;
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java
new file mode 100644
index 0000000..0d0d2ab
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/RestconfService.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.Encoded;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.Patch;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+/**
+ * The URI hierarchy for the RESTCONF resources consists of an entry point
+ * container, 4 top-level resources, and 1 field.
+ * <ul>
+ * <li><b>/restconf</b> - {@link #getRoot()}
+ * <ul>
+ * <li><b>/config</b> - {@link #readConfigurationData(String, UriInfo)}
+ * {@link #updateConfigurationData(String, NormalizedNodeContext, UriInfo)}
+ * {@link #createConfigurationData(NormalizedNodeContext, UriInfo)}
+ * {@link #createConfigurationData(String, NormalizedNodeContext, UriInfo)}
+ * {@link #deleteConfigurationData(String)}
+ * <li><b>/operational</b> - {@link #readOperationalData(String, UriInfo)}
+ * <li>/modules - {@link #getModules(UriInfo)}
+ * <ul>
+ * <li>/module
+ * </ul>
+ * <li><b>/operations</b> -
+ * {@link #invokeRpc(String, NormalizedNodeContext, UriInfo)}
+ * {@link #invokeRpc(String, NormalizedNodeContext, UriInfo)}
+ * <li>/version (field)
+ * </ul>
+ * </ul>
+ */
+@Path("/")
+public interface RestconfService {
+
+ String XML = "+xml";
+ String JSON = "+json";
+
+ @GET
+ Object getRoot();
+
+ /**
+ * Get all modules supported by controller.
+ *
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModules(@Context UriInfo uriInfo);
+
+ /**
+ * Get all modules supported by mount point.
+ *
+ * @param identifier
+ * mount point identifier
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModules(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * Get module.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/modules/module/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getModule(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * List of rpc or action operations supported by the server.
+ *
+ * @return A JSON document string
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations")
+ @Produces({ Draft02.MediaTypes.API + JSON, MediaType.APPLICATION_JSON })
+ String getOperationsJSON();
+
+ /**
+ * List of rpc or action operations supported by the server.
+ *
+ * @return A XML document string
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations")
+ @Produces({ Draft02.MediaTypes.API + XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ String getOperationsXML();
+
+ /**
+ * Valid for mount points. List of operations supported by the server.
+ *
+ * @param identifier
+ * path parameter
+ * @param uriInfo
+ * URI information
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfOperationsService#getOperations(String, UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operations/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getOperations(@PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * Invoke RPC operation.
+ *
+ * @param identifier
+ * module name and rpc identifier string for the desired operation
+ * @param payload
+ * {@link NormalizedNodeContext} - the body of the operation
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfInvokeOperationsService#invokeRpc(String, NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/operations/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.OPERATION + JSON,
+ Draft02.MediaTypes.OPERATION + XML,
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ @Consumes({
+ Draft02.MediaTypes.OPERATION + JSON,
+ Draft02.MediaTypes.OPERATION + XML,
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext invokeRpc(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get target data resource from config data store.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/config/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext readConfigurationData(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get target data resource from operational data store.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/operational/{identifier:.+}")
+ @Produces({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext readOperationalData(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Create or replace the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @param payload
+ * data node for put to config DS
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#putData(String,
+ * NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @PUT
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response updateConfigurationData(@Encoded @PathParam("identifier") String identifier,
+ NormalizedNodeContext payload, @Context UriInfo uriInfo);
+
+ /**
+ * Create a data resource in target.
+ *
+ * @param identifier
+ * path to target
+ * @param payload
+ * new data
+ * @param uriInfo
+ * URI info
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#postData(String,
+ * NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response createConfigurationData(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Create a data resource.
+ *
+ * @param payload
+ * new data
+ * @param uriInfo
+ * URI info
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfDataService#postData(NormalizedNodeContext, UriInfo)
+ */
+ @Deprecated
+ @POST
+ @Path("/config")
+ @Consumes({
+ Draft02.MediaTypes.DATA + JSON,
+ Draft02.MediaTypes.DATA + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ Response createConfigurationData(NormalizedNodeContext payload, @Context UriInfo uriInfo);
+
+ /**
+ * Delete the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @return {@link Response}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#deleteData(String)
+ */
+ @Deprecated
+ @DELETE
+ @Path("/config/{identifier:.+}")
+ Response deleteConfigurationData(@Encoded @PathParam("identifier") String identifier);
+
+ /**
+ * Subscribe to stream.
+ *
+ * @param identifier
+ * stream identifier
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by
+ * RestconfStreamsSubscriptionService#subscribeToStream(String, UriInfo)
+ */
+ @Deprecated
+ @GET
+ @Path("/streams/stream/{identifier:.+}")
+ NormalizedNodeContext subscribeToStream(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+
+ /**
+ * Get list of all streams.
+ *
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#readData(String,
+ * UriInfo)
+ **/
+ @Deprecated
+ @GET
+ @Path("/streams")
+ @Produces({
+ Draft02.MediaTypes.API + JSON,
+ Draft02.MediaTypes.API + XML,
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+ })
+ NormalizedNodeContext getAvailableStreams(@Context UriInfo uriInfo);
+
+ /**
+ * Ordered list of edits that are applied to the target datastore by the server.
+ *
+ * @param identifier
+ * path to target
+ * @param context
+ * edits
+ * @param uriInfo
+ * URI info
+ * @return {@link PatchStatusContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#patchData(String,
+ * PatchContext, UriInfo)
+ */
+ @Deprecated
+ @Patch
+ @Path("/config/{identifier:.+}")
+ @Consumes({
+ MediaTypes.PATCH + JSON,
+ MediaTypes.PATCH + XML
+ })
+ @Produces({
+ MediaTypes.PATCH_STATUS + JSON,
+ MediaTypes.PATCH_STATUS + XML
+ })
+ PatchStatusContext patchConfigurationData(@Encoded @PathParam("identifier") String identifier, PatchContext
+ context, @Context UriInfo uriInfo);
+
+ /**
+ * Ordered list of edits that are applied to the datastore by the server.
+ *
+ * @param context
+ * edits
+ * @param uriInfo
+ * URI info
+ * @return {@link PatchStatusContext}
+ * @deprecated do not use this method. It will be replaced by RestconfDataService#patchData(PatchContext,
+ * UriInfo)
+ */
+ @Deprecated
+ @Patch
+ @Path("/config")
+ @Consumes({
+ MediaTypes.PATCH + JSON,
+ MediaTypes.PATCH + XML
+ })
+ @Produces({
+ MediaTypes.PATCH_STATUS + JSON,
+ MediaTypes.PATCH_STATUS + XML
+ })
+ PatchStatusContext patchConfigurationData(PatchContext context, @Context UriInfo uriInfo);
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java
new file mode 100644
index 0000000..b765331
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/api/package-info.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.api;
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java
new file mode 100644
index 0000000..76ffdb4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+
+/**
+ * JAX-RS Provider.
+ *
+ * @deprecated This class will be replaced by AbstractIdentifierAwareJaxRsProvider in restconf-nb-rfc8040
+ */
+@Deprecated
+public class AbstractIdentifierAwareJaxRsProvider {
+
+ private static final String POST = "POST";
+
+ @Context
+ private UriInfo uriInfo;
+
+ @Context
+ private Request request;
+
+ private final ControllerContext controllerContext;
+
+ protected AbstractIdentifierAwareJaxRsProvider(final ControllerContext controllerContext) {
+ this.controllerContext = controllerContext;
+ }
+
+ protected final String getIdentifier() {
+ return uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
+ }
+
+ protected InstanceIdentifierContext getInstanceIdentifierContext() {
+ return controllerContext.toInstanceIdentifier(getIdentifier());
+ }
+
+ protected UriInfo getUriInfo() {
+ return uriInfo;
+ }
+
+ protected boolean isPost() {
+ return POST.equals(request.getMethod());
+ }
+
+ protected ControllerContext getControllerContext() {
+ return controllerContext;
+ }
+
+ Request getRequest() {
+ return request;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java
new file mode 100644
index 0000000..b9d7a7a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/DepthAwareNormalizedNodeWriter.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
+
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+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.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an experimental iterator over a {@link NormalizedNode}. This is essentially the opposite of a
+ * {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over the backing data, this
+ * encapsulates a {@link NormalizedNodeStreamWriter} and allows us to write multiple nodes.
+ *
+ * @deprecated This class will be replaced by ParameterAwareNormalizedNodeWriter in restconf-nb-rfc8040
+ */
+@Deprecated
+public class DepthAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
+ private final NormalizedNodeStreamWriter writer;
+ protected int currentDepth = 0;
+ protected final int maxDepth;
+
+ private DepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
+ this.writer = requireNonNull(writer);
+ this.maxDepth = maxDepth;
+ }
+
+ protected final NormalizedNodeStreamWriter getWriter() {
+ return writer;
+ }
+
+ /**
+ * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+ *
+ * @param writer Back-end writer
+ * @return A new instance.
+ */
+ public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final int maxDepth) {
+ return forStreamWriter(writer, true, maxDepth);
+ }
+
+ /**
+ * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
+ * Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter, int)}
+ * method, this allows the caller to switch off RFC6020 XML compliance, providing better
+ * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
+ * to emit leaf nodes which participate in a list's key first and in the order in which
+ * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
+ * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
+ * for each key and then for each emitted node we need to check whether it was already
+ * emitted.
+ *
+ * @param writer Back-end writer
+ * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
+ * @return A new instance.
+ */
+ public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final boolean orderKeyLeaves, final int maxDepth) {
+ return orderKeyLeaves ? new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth)
+ : new DepthAwareNormalizedNodeWriter(writer, maxDepth);
+ }
+
+ /**
+ * Iterate over the provided {@link NormalizedNode} and emit write
+ * events to the encapsulated {@link NormalizedNodeStreamWriter}.
+ *
+ * @param node Node
+ * @return DepthAwareNormalizedNodeWriter
+ * @throws IOException when thrown from the backing writer.
+ */
+ @Override
+ public final DepthAwareNormalizedNodeWriter write(final NormalizedNode node) throws IOException {
+ if (wasProcessedAsCompositeNode(node)) {
+ return this;
+ }
+
+ if (wasProcessAsSimpleNode(node)) {
+ return this;
+ }
+
+ throw new IllegalStateException("It wasn't possible to serialize node " + node);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ writer.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ writer.flush();
+ writer.close();
+ }
+
+ /**
+ * Emit a best guess of a hint for a particular set of children. It evaluates the
+ * iterable to see if the size can be easily gotten to. If it is, we hint at the
+ * real number of child nodes. Otherwise we emit UNKNOWN_SIZE.
+ *
+ * @param children Child nodes
+ * @return Best estimate of the collection size required to hold all the children.
+ */
+ static final int childSizeHint(final Iterable<?> children) {
+ return children instanceof Collection ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
+ }
+
+ private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException {
+ if (node instanceof LeafSetEntryNode) {
+ if (currentDepth < maxDepth) {
+ final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
+ writer.startLeafSetEntryNode(nodeAsLeafList.getIdentifier());
+ writer.scalarValue(nodeAsLeafList.body());
+ writer.endNode();
+ }
+ return true;
+ } else if (node instanceof LeafNode) {
+ final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
+ writer.startLeafNode(nodeAsLeaf.getIdentifier());
+ writer.scalarValue(nodeAsLeaf.body());
+ writer.endNode();
+ return true;
+ } else if (node instanceof AnyxmlNode) {
+ final AnyxmlNode<?> anyxmlNode = (AnyxmlNode<?>)node;
+ final Class<?> objectModel = anyxmlNode.bodyObjectModel();
+ if (writer.startAnyxmlNode(anyxmlNode.getIdentifier(), objectModel)) {
+ if (DOMSource.class.isAssignableFrom(objectModel)) {
+ writer.domSourceValue((DOMSource) anyxmlNode.body());
+ } else {
+ writer.scalarValue(anyxmlNode.body());
+ }
+ writer.endNode();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Emit events for all children and then emit an endNode() event.
+ *
+ * @param children Child iterable
+ * @return True
+ * @throws IOException when the writer reports it
+ */
+ protected final boolean writeChildren(final Iterable<? extends NormalizedNode> children) throws IOException {
+ if (currentDepth < maxDepth) {
+ for (final NormalizedNode child : children) {
+ write(child);
+ }
+ }
+ writer.endNode();
+ return true;
+ }
+
+ protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
+ if (currentDepth < maxDepth) {
+ writeChildren(mapEntryNode.body());
+ } else if (currentDepth == maxDepth) {
+ writeOnlyKeys(mapEntryNode.getIdentifier().entrySet());
+ }
+ return true;
+ }
+
+ private void writeOnlyKeys(final Set<Entry<QName, Object>> entries) throws IOException {
+ for (final Entry<QName, Object> entry : entries) {
+ writer.startLeafNode(new NodeIdentifier(entry.getKey()));
+ writer.scalarValue(entry.getValue());
+ writer.endNode();
+ }
+ writer.endNode();
+ }
+
+ protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+ writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
+ currentDepth++;
+ writeMapEntryChildren(node);
+ currentDepth--;
+ return true;
+ }
+
+ private boolean wasProcessedAsCompositeNode(final NormalizedNode node) throws IOException {
+ boolean processedAsCompositeNode = false;
+ if (node instanceof ContainerNode) {
+ final ContainerNode n = (ContainerNode) node;
+ writer.startContainerNode(n.getIdentifier(), childSizeHint(n.body()));
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ } else if (node instanceof MapEntryNode) {
+ processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
+ } else if (node instanceof UnkeyedListEntryNode) {
+ final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
+ writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.body()));
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ } else if (node instanceof ChoiceNode) {
+ final ChoiceNode n = (ChoiceNode) node;
+ writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof AugmentationNode) {
+ final AugmentationNode n = (AugmentationNode) node;
+ writer.startAugmentationNode(n.getIdentifier());
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof UnkeyedListNode) {
+ final UnkeyedListNode n = (UnkeyedListNode) node;
+ writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof UserMapNode) {
+ final UserMapNode n = (UserMapNode) node;
+ writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof MapNode) {
+ final MapNode n = (MapNode) node;
+ writer.startMapNode(n.getIdentifier(), childSizeHint(n.body()));
+ processedAsCompositeNode = writeChildren(n.body());
+ } else if (node instanceof LeafSetNode) {
+ final LeafSetNode<?> n = (LeafSetNode<?>) node;
+ if (node instanceof UserLeafSetNode) {
+ writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.body()));
+ } else {
+ writer.startLeafSet(n.getIdentifier(), childSizeHint(n.body()));
+ }
+ currentDepth++;
+ processedAsCompositeNode = writeChildren(n.body());
+ currentDepth--;
+ }
+
+ return processedAsCompositeNode;
+ }
+
+ private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
+ private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
+
+ OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
+ super(writer, maxDepth);
+ }
+
+ @Override
+ protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
+ final NormalizedNodeStreamWriter writer = getWriter();
+ writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
+
+ final Set<QName> qnames = node.getIdentifier().keySet();
+ // Write out all the key children
+ for (final QName qname : qnames) {
+ final Optional<? extends NormalizedNode> child = node.findChildByArg(new NodeIdentifier(qname));
+ if (child.isPresent()) {
+ write(child.get());
+ } else {
+ LOG.info("No child for key element {} found", qname);
+ }
+ }
+
+ // Write all the rest
+ currentDepth++;
+ final boolean result = writeChildren(Iterables.filter(node.body(), input -> {
+ if (input instanceof AugmentationNode) {
+ return true;
+ }
+ if (!qnames.contains(input.getIdentifier().getNodeType())) {
+ return true;
+ }
+
+ LOG.debug("Skipping key child {}", input);
+ return false;
+ }));
+ currentDepth--;
+ return result;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
new file mode 100644
index 0000000..aecea7a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({
+ Draft02.MediaTypes.DATA + RestconfService.JSON,
+ Draft02.MediaTypes.OPERATION + RestconfService.JSON,
+ MediaType.APPLICATION_JSON
+})
+public class JsonNormalizedNodeBodyReader
+ extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<NormalizedNodeContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JsonNormalizedNodeBodyReader.class);
+
+ public JsonNormalizedNodeBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws
+ WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream, isPost());
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public static NormalizedNodeContext readFrom(final String uriPath, final InputStream entityStream,
+ final boolean isPost, final ControllerContext controllerContext) throws RestconfDocumentedException {
+
+ try {
+ return readFrom(controllerContext.toInstanceIdentifier(uriPath), entityStream, isPost);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private static NormalizedNodeContext readFrom(final InstanceIdentifierContext path,
+ final InputStream entityStream, final boolean isPost)
+ throws IOException {
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ return new NormalizedNodeContext(path, null);
+ }
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ final Inference parentInference;
+ if (isPost && !(path.getSchemaNode() instanceof RpcDefinition)) {
+ parentInference = path.inference();
+ } else {
+ final var inference = path.inference();
+ if (!inference.statementPath().isEmpty()) {
+ final var stack = inference.toSchemaInferenceStack();
+ stack.exit();
+ parentInference = stack.toInference();
+ } else {
+ parentInference = inference;
+ }
+ }
+
+ final JsonParserStream jsonParser = JsonParserStream.create(writer,
+ JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(path.getSchemaContext()),
+ parentInference);
+ final JsonReader reader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get(),
+ StandardCharsets.UTF_8));
+ jsonParser.parse(reader);
+
+ NormalizedNode result = resultHolder.getResult();
+ final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
+ InstanceIdentifierContext newIIContext;
+
+ while (result instanceof AugmentationNode || result instanceof ChoiceNode) {
+ final Object childNode = ((DataContainerNode) result).body().iterator().next();
+ if (isPost) {
+ iiToDataList.add(result.getIdentifier());
+ }
+ result = (NormalizedNode) childNode;
+ }
+
+ if (isPost) {
+ if (result instanceof MapEntryNode) {
+ iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(result.getIdentifier().getNodeType()));
+ iiToDataList.add(result.getIdentifier());
+ } else {
+ iiToDataList.add(result.getIdentifier());
+ }
+ } else {
+ if (result instanceof MapNode) {
+ result = Iterables.getOnlyElement(((MapNode) result).body());
+ }
+ }
+
+ return new NormalizedNodeContext(path.withConcatenatedArgs(iiToDataList), result);
+ }
+
+ private static void propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+ Throwables.throwIfInstanceOf(exception, RestconfDocumentedException.class);
+ LOG.debug("Error parsing json input", exception);
+
+ if (exception instanceof ResultAlreadySetException) {
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. "
+ + "Are you creating multiple resources/subresources in POST request?", exception);
+ }
+
+ RestconfDocumentedException.throwIfYangError(exception);
+ throw new RestconfDocumentedException("Error parsing input: " + exception.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, exception);
+ }
+}
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
new file mode 100644
index 0000000..ce5a8c4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Patch reader for JSON.
+ *
+ * @deprecated This class will be replaced by JsonToPatchBodyReader in restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON})
+public class JsonToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider
+ implements MessageBodyReader<PatchContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JsonToPatchBodyReader.class);
+
+ public JsonToPatchBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchContext readFrom(final Class<PatchContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+ throws WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream);
+ } catch (final Exception e) {
+ throw propagateExceptionAs(e);
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public PatchContext readFrom(final String uriPath, final InputStream entityStream) throws
+ RestconfDocumentedException {
+ try {
+ return readFrom(getControllerContext().toInstanceIdentifier(uriPath), entityStream);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private PatchContext readFrom(final InstanceIdentifierContext path, final InputStream entityStream)
+ throws IOException {
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ return new PatchContext(path, null, null);
+ }
+
+ final JsonReader jsonReader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get(),
+ StandardCharsets.UTF_8));
+ AtomicReference<String> patchId = new AtomicReference<>();
+ final List<PatchEntity> resultList = read(jsonReader, path, patchId);
+ jsonReader.close();
+
+ return new PatchContext(path, resultList, patchId.get());
+ }
+
+ private static RuntimeException propagateExceptionAs(final Exception exception) throws RestconfDocumentedException {
+ Throwables.throwIfInstanceOf(exception, RestconfDocumentedException.class);
+ LOG.debug("Error parsing json input", exception);
+
+ if (exception instanceof ResultAlreadySetException) {
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+ }
+
+ RestconfDocumentedException.throwIfYangError(exception);
+ throw new RestconfDocumentedException("Error parsing json input: " + exception.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, exception);
+ }
+
+ private List<PatchEntity> read(final JsonReader in, final InstanceIdentifierContext path,
+ final AtomicReference<String> patchId) throws IOException {
+ final List<PatchEntity> resultCollection = new ArrayList<>();
+ final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+ path.getSchemaContext());
+ final JsonToPatchBodyReader.PatchEdit edit = new JsonToPatchBodyReader.PatchEdit();
+
+ while (in.hasNext()) {
+ switch (in.peek()) {
+ case STRING:
+ case NUMBER:
+ in.nextString();
+ break;
+ case BOOLEAN:
+ Boolean.toString(in.nextBoolean());
+ break;
+ case NULL:
+ in.nextNull();
+ break;
+ case BEGIN_ARRAY:
+ in.beginArray();
+ break;
+ case BEGIN_OBJECT:
+ in.beginObject();
+ break;
+ case END_DOCUMENT:
+ break;
+ case NAME:
+ parseByName(in.nextName(), edit, in, path, codec, resultCollection, patchId);
+ break;
+ case END_OBJECT:
+ in.endObject();
+ break;
+ case END_ARRAY:
+ in.endArray();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return ImmutableList.copyOf(resultCollection);
+ }
+
+ /**
+ * Switch value of parsed JsonToken.NAME and read edit definition or patch id.
+ *
+ * @param name value of token
+ * @param edit PatchEdit instance
+ * @param in JsonReader reader
+ * @param path InstanceIdentifierContext context
+ * @param codec StringModuleInstanceIdentifierCodec codec
+ * @param resultCollection collection of parsed edits
+ * @throws IOException if operation fails
+ */
+ private void parseByName(final @NonNull String name, final @NonNull PatchEdit edit,
+ final @NonNull JsonReader in, final @NonNull InstanceIdentifierContext path,
+ final @NonNull StringModuleInstanceIdentifierCodec codec,
+ final @NonNull List<PatchEntity> resultCollection,
+ final @NonNull AtomicReference<String> patchId) throws IOException {
+ switch (name) {
+ case "edit" :
+ if (in.peek() == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+
+ while (in.hasNext()) {
+ readEditDefinition(edit, in, path, codec);
+ resultCollection.add(prepareEditOperation(edit));
+ edit.clear();
+ }
+
+ in.endArray();
+ } else {
+ readEditDefinition(edit, in, path, codec);
+ resultCollection.add(prepareEditOperation(edit));
+ edit.clear();
+ }
+
+ break;
+ case "patch-id" :
+ patchId.set(in.nextString());
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Read one patch edit object from Json input.
+ * @param edit PatchEdit instance to be filled with read data
+ * @param in JsonReader reader
+ * @param path InstanceIdentifierContext path context
+ * @param codec StringModuleInstanceIdentifierCodec codec
+ * @throws IOException if operation fails
+ */
+ private void readEditDefinition(final @NonNull PatchEdit edit, final @NonNull JsonReader in,
+ final @NonNull InstanceIdentifierContext path,
+ final @NonNull StringModuleInstanceIdentifierCodec codec) throws IOException {
+ final StringBuilder value = new StringBuilder();
+ in.beginObject();
+
+ while (in.hasNext()) {
+ final String editDefinition = in.nextName();
+ switch (editDefinition) {
+ case "edit-id" :
+ edit.setId(in.nextString());
+ break;
+ case "operation" :
+ edit.setOperation(PatchEditOperation.valueOf(in.nextString().toUpperCase(Locale.ROOT)));
+ break;
+ case "target" :
+ // target can be specified completely in request URI
+ final String target = in.nextString();
+ if (target.equals("/")) {
+ edit.setTarget(path.getInstanceIdentifier());
+ edit.setTargetSchemaNode(SchemaInferenceStack.of(path.getSchemaContext()).toInference());
+ } else {
+ edit.setTarget(codec.deserialize(codec.serialize(path.getInstanceIdentifier()).concat(target)));
+
+ final var stack = codec.getDataContextTree().enterPath(edit.getTarget()).orElseThrow().stack();
+ stack.exit();
+ final EffectiveStatement<?, ?> parentStmt = stack.currentStatement();
+ verify(parentStmt instanceof SchemaNode, "Unexpected parent %s", parentStmt);
+ edit.setTargetSchemaNode(stack.toInference());
+ }
+
+ break;
+ case "value" :
+ // save data defined in value node for next (later) processing, because target needs to be read
+ // always first and there is no ordering in Json input
+ readValueNode(value, in);
+ break;
+ default:
+ break;
+ }
+ }
+
+ in.endObject();
+
+ // read saved data to normalized node when target schema is already known
+ edit.setData(readEditData(new JsonReader(
+ new StringReader(value.toString())), edit.getTargetSchemaNode(), path));
+ }
+
+ /**
+ * Parse data defined in value node and saves it to buffer.
+ * @param value Buffer to read value node
+ * @param in JsonReader reader
+ * @throws IOException if operation fails
+ */
+ private void readValueNode(final @NonNull StringBuilder value, final @NonNull JsonReader in) throws IOException {
+ in.beginObject();
+ value.append('{');
+
+ value.append('"').append(in.nextName()).append("\":");
+
+ if (in.peek() == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+ value.append('[');
+
+ while (in.hasNext()) {
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ readValueObject(value, in);
+ }
+ if (in.peek() != JsonToken.END_ARRAY) {
+ value.append(',');
+ }
+ }
+
+ in.endArray();
+ value.append(']');
+ } else {
+ readValueObject(value, in);
+ }
+
+ in.endObject();
+ value.append('}');
+ }
+
+ /**
+ * Parse one value object of data and saves it to buffer.
+ * @param value Buffer to read value object
+ * @param in JsonReader reader
+ * @throws IOException if operation fails
+ */
+ private void readValueObject(final @NonNull StringBuilder value, final @NonNull JsonReader in) throws IOException {
+ // read simple leaf value
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ return;
+ }
+
+ in.beginObject();
+ value.append('{');
+
+ while (in.hasNext()) {
+ value.append('"').append(in.nextName()).append("\":");
+
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ if (in.peek() == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+ value.append('[');
+
+ while (in.hasNext()) {
+ if (in.peek() == JsonToken.STRING) {
+ value.append('"').append(in.nextString()).append('"');
+ } else {
+ readValueObject(value, in);
+ }
+ if (in.peek() != JsonToken.END_ARRAY) {
+ value.append(',');
+ }
+ }
+
+ in.endArray();
+ value.append(']');
+ } else {
+ readValueObject(value, in);
+ }
+ }
+
+ if (in.peek() != JsonToken.END_OBJECT) {
+ value.append(',');
+ }
+ }
+
+ in.endObject();
+ value.append('}');
+ }
+
+ /**
+ * Read patch edit data defined in value node to NormalizedNode.
+ * @param in reader JsonReader reader
+ * @return NormalizedNode representing data
+ */
+ private static NormalizedNode readEditData(final @NonNull JsonReader in,
+ final @NonNull Inference targetSchemaNode, final @NonNull InstanceIdentifierContext path) {
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+ final EffectiveModelContext context = path.getSchemaContext();
+ JsonParserStream.create(writer, JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context),
+ targetSchemaNode)
+ .parse(in);
+
+ return resultHolder.getResult();
+ }
+
+ /**
+ * Prepare PatchEntity from PatchEdit instance when it satisfies conditions, otherwise throws exception.
+ * @param edit Instance of PatchEdit
+ * @return PatchEntity Patch entity
+ */
+ private static PatchEntity prepareEditOperation(final @NonNull PatchEdit edit) {
+ if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
+ && checkDataPresence(edit.getOperation(), edit.getData() != null)) {
+ if (edit.getOperation().isWithValue()) {
+ // for lists allow to manipulate with list items through their parent
+ final YangInstanceIdentifier targetNode;
+ if (edit.getTarget().getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+ targetNode = edit.getTarget().getParent();
+ } else {
+ targetNode = edit.getTarget();
+ }
+
+ return new PatchEntity(edit.getId(), edit.getOperation(), targetNode, edit.getData());
+ }
+
+ return new PatchEntity(edit.getId(), edit.getOperation(), edit.getTarget());
+ }
+
+ throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ /**
+ * Check if data is present when operation requires it and not present when operation data is not allowed.
+ * @param operation Name of operation
+ * @param hasData Data in edit are present/not present
+ * @return true if data is present when operation requires it or if there are no data when operation does not
+ * allow it, false otherwise
+ */
+ private static boolean checkDataPresence(final @NonNull PatchEditOperation operation, final boolean hasData) {
+ return operation.isWithValue() == hasData;
+ }
+
+ /**
+ * Helper class representing one patch edit.
+ */
+ private static final class PatchEdit {
+ private String id;
+ private PatchEditOperation operation;
+ private YangInstanceIdentifier target;
+ private Inference targetSchemaNode;
+ private NormalizedNode data;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public PatchEditOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(final PatchEditOperation operation) {
+ this.operation = operation;
+ }
+
+ public YangInstanceIdentifier getTarget() {
+ return target;
+ }
+
+ public void setTarget(final YangInstanceIdentifier target) {
+ this.target = target;
+ }
+
+ public Inference getTargetSchemaNode() {
+ return targetSchemaNode;
+ }
+
+ public void setTargetSchemaNode(final Inference targetSchemaNode) {
+ this.targetSchemaNode = targetSchemaNode;
+ }
+
+ public NormalizedNode getData() {
+ return data;
+ }
+
+ public void setData(final NormalizedNode data) {
+ this.data = data;
+ }
+
+ public void clear() {
+ id = null;
+ operation = null;
+ target = null;
+ targetSchemaNode = null;
+ data = null;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java
new file mode 100644
index 0000000..d52de7d
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeContext.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@Deprecated(forRemoval = true, since = "2.0.6")
+// Non-final for mocking
+public class NormalizedNodeContext {
+ private final InstanceIdentifierContext context;
+ private final ImmutableMap<String, Object> headers;
+ private final WriterParameters writerParameters;
+ private final NormalizedNode data;
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final WriterParameters writerParameters,
+ final ImmutableMap<String, Object> headers) {
+ this.context = context;
+ this.data = data;
+ this.writerParameters = writerParameters;
+ this.headers = requireNonNull(headers);
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final WriterParameters writerParameters) {
+ this(context, data, writerParameters, ImmutableMap.of());
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data) {
+ this(context, data, WriterParameters.EMPTY, ImmutableMap.of());
+ }
+
+ public NormalizedNodeContext(final InstanceIdentifierContext context,
+ final NormalizedNode data, final ImmutableMap<String, Object> headers) {
+ this(context, data, WriterParameters.EMPTY, headers);
+ }
+
+ public InstanceIdentifierContext getInstanceIdentifierContext() {
+ return context;
+ }
+
+ public NormalizedNode getData() {
+ return data;
+ }
+
+ public WriterParameters getWriterParameters() {
+ return writerParameters;
+ }
+
+ /**
+ * Return headers of {@code NormalizedNodeContext}.
+ *
+ * @return map of headers
+ */
+ public ImmutableMap<String, Object> getNewHeaders() {
+ return headers;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java
new file mode 100644
index 0000000..f754df8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeJsonBodyWriter.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map.Entry;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.XMLStreamException;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.util.NetconfUtil;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+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.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.xml.sax.SAXException;
+
+/**
+ * Normalized node writer for JSON.
+ *
+ * @deprecated This class will be replaced by NormalizedNodeJsonBodyWriter from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Produces({
+ Draft02.MediaTypes.API + RestconfService.JSON,
+ Draft02.MediaTypes.DATA + RestconfService.JSON,
+ Draft02.MediaTypes.OPERATION + RestconfService.JSON,
+ MediaType.APPLICATION_JSON
+})
+public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
+
+ private static final int DEFAULT_INDENT_SPACES_NUM = 2;
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return type.equals(NormalizedNodeContext.class);
+ }
+
+ @Override
+ public long getSize(final NormalizedNodeContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final NormalizedNodeContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException, WebApplicationException {
+ if (httpHeaders != null) {
+ for (final Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
+ httpHeaders.add(entry.getKey(), entry.getValue());
+ }
+ }
+ NormalizedNode data = context.getData();
+ if (data == null) {
+ return;
+ }
+
+ final InstanceIdentifierContext identifierCtx = context.getInstanceIdentifierContext();
+
+ try (JsonWriter jsonWriter = createJsonWriter(entityStream, context.getWriterParameters().isPrettyPrint())) {
+ jsonWriter.beginObject();
+ writeNormalizedNode(jsonWriter, identifierCtx, data, context.getWriterParameters().getDepth());
+ jsonWriter.endObject();
+ jsonWriter.flush();
+ }
+ }
+
+ private static void writeNormalizedNode(final JsonWriter jsonWriter, final InstanceIdentifierContext context,
+ // Note: mutable argument
+ NormalizedNode data, final @Nullable Integer depth) throws IOException {
+
+ final var stack = context.inference().toSchemaInferenceStack();
+ final RestconfNormalizedNodeWriter nnWriter;
+ if (stack.isEmpty()) {
+ /*
+ * Creates writer without initialNs and we write children of root data container
+ * which is not visible in restconf
+ */
+ nnWriter = createNormalizedNodeWriter(context, context.inference(), jsonWriter, depth);
+ if (data instanceof ContainerNode) {
+ writeChildren(nnWriter,(ContainerNode) data);
+ } else if (data instanceof DOMSourceAnyxmlNode) {
+ try {
+ writeChildren(nnWriter,
+ (ContainerNode) NetconfUtil.transformDOMSourceToNormalizedNode(
+ context.getSchemaContext(), ((DOMSourceAnyxmlNode)data).body()).getResult());
+ } catch (XMLStreamException | URISyntaxException | SAXException e) {
+ throw new IOException("Cannot write anyxml.", e);
+ }
+ }
+ } else if (context.getSchemaNode() instanceof RpcDefinition) {
+ /*
+ * RpcDefinition is not supported as initial codec in JSONStreamWriter,
+ * so we need to emit initial output declaratation..
+ */
+ final var rpc = (RpcDefinition) context.getSchemaNode();
+ final var tmp = SchemaInferenceStack.of(context.getSchemaContext());
+ tmp.enterSchemaTree(rpc.getQName());
+ tmp.enterSchemaTree(rpc.getOutput().getQName());
+
+ nnWriter = createNormalizedNodeWriter(context, tmp.toInference(), jsonWriter, depth);
+ jsonWriter.name("output");
+ jsonWriter.beginObject();
+ writeChildren(nnWriter, (ContainerNode) data);
+ jsonWriter.endObject();
+ } else {
+ stack.exit();
+
+ if (data instanceof MapEntryNode) {
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .withChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter = createNormalizedNodeWriter(context, stack.toInference(), jsonWriter, depth);
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ }
+
+ private static void writeChildren(final RestconfNormalizedNodeWriter nnWriter, final ContainerNode data)
+ throws IOException {
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ }
+ }
+
+ private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(
+ final InstanceIdentifierContext context, final Inference inference, final JsonWriter jsonWriter,
+ final @Nullable Integer depth) {
+
+ final SchemaNode schema = context.getSchemaNode();
+ final JSONCodecFactory codecs = getCodecFactory(context);
+
+ final XMLNamespace initialNs;
+ if (schema instanceof DataSchemaNode && !((DataSchemaNode)schema).isAugmenting()
+ && !(schema instanceof SchemaContext) || schema instanceof RpcDefinition) {
+ initialNs = schema.getQName().getNamespace();
+ } else {
+ initialNs = null;
+ }
+ final NormalizedNodeStreamWriter streamWriter =
+ JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference, initialNs, jsonWriter);
+ if (depth != null) {
+ return DepthAwareNormalizedNodeWriter.forStreamWriter(streamWriter, depth);
+ }
+
+ return RestconfDelegatingNormalizedNodeWriter.forStreamWriter(streamWriter);
+ }
+
+ private static JsonWriter createJsonWriter(final OutputStream entityStream, final boolean prettyPrint) {
+ if (prettyPrint) {
+ return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8),
+ DEFAULT_INDENT_SPACES_NUM);
+ }
+
+ return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8));
+ }
+
+ private static JSONCodecFactory getCodecFactory(final InstanceIdentifierContext context) {
+ // TODO: Performance: Cache JSON Codec factory and schema context
+ return JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java
new file mode 100644
index 0000000..e0ba9d9
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/NormalizedNodeXmlBodyWriter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map.Entry;
+import javanet.staxutils.IndentingXMLStreamWriter;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.util.NetconfUtil;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
+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.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.xml.sax.SAXException;
+
+/**
+ * Normalized node writer for XML.
+ *
+ * @deprecated This class will be replaced by NormalizedNodeXmlBodyWriter from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Produces({
+ Draft02.MediaTypes.API + RestconfService.XML,
+ Draft02.MediaTypes.DATA + RestconfService.XML,
+ Draft02.MediaTypes.OPERATION + RestconfService.XML,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+})
+public class NormalizedNodeXmlBodyWriter implements MessageBodyWriter<NormalizedNodeContext> {
+
+ private static final XMLOutputFactory XML_FACTORY;
+
+ static {
+ XML_FACTORY = XMLOutputFactory.newFactory();
+ XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return type.equals(NormalizedNodeContext.class);
+ }
+
+ @Override
+ public long getSize(final NormalizedNodeContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final NormalizedNodeContext context, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException,
+ WebApplicationException {
+ for (final Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
+ httpHeaders.add(entry.getKey(), entry.getValue());
+ }
+ final InstanceIdentifierContext pathContext = context.getInstanceIdentifierContext();
+ if (context.getData() == null) {
+ return;
+ }
+
+ XMLStreamWriter xmlWriter;
+ try {
+ xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+ if (context.getWriterParameters().isPrettyPrint()) {
+ xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
+ }
+ } catch (final XMLStreamException | FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+ final NormalizedNode data = context.getData();
+
+ writeNormalizedNode(xmlWriter, pathContext.inference().toSchemaInferenceStack(), pathContext, data,
+ context.getWriterParameters().getDepth());
+ }
+
+ private static void writeNormalizedNode(final XMLStreamWriter xmlWriter, final SchemaInferenceStack stack,
+ final InstanceIdentifierContext pathContext, NormalizedNode data, final @Nullable Integer depth)
+ throws IOException {
+ final RestconfNormalizedNodeWriter nnWriter;
+ final EffectiveModelContext schemaCtx = pathContext.getSchemaContext();
+ if (stack.isEmpty()) {
+ nnWriter = createNormalizedNodeWriter(xmlWriter, pathContext.inference(), depth);
+ if (data instanceof DOMSourceAnyxmlNode) {
+ try {
+ writeElements(xmlWriter, nnWriter,
+ (ContainerNode) NetconfUtil.transformDOMSourceToNormalizedNode(schemaCtx,
+ ((DOMSourceAnyxmlNode)data).body()).getResult());
+ } catch (XMLStreamException | URISyntaxException | SAXException e) {
+ throw new IOException("Cannot write anyxml", e);
+ }
+ } else {
+ writeElements(xmlWriter, nnWriter, (ContainerNode) data);
+ }
+ } else if (pathContext.getSchemaNode() instanceof RpcDefinition) {
+ final var rpc = (RpcDefinition) pathContext.getSchemaNode();
+ final var tmp = SchemaInferenceStack.of(pathContext.getSchemaContext());
+ tmp.enterSchemaTree(rpc.getQName());
+ tmp.enterSchemaTree(rpc.getOutput().getQName());
+
+ nnWriter = createNormalizedNodeWriter(xmlWriter, tmp.toInference(), depth);
+ writeElements(xmlWriter, nnWriter, (ContainerNode) data);
+ } else {
+ stack.exit();
+ nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth);
+ if (data instanceof MapEntryNode) {
+ // Restconf allows returning one list item. We need to wrap it
+ // in map node in order to serialize it properly
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .addChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ }
+
+ private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
+ final Inference inference, final @Nullable Integer depth) {
+ final NormalizedNodeStreamWriter xmlStreamWriter =
+ XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference);
+ if (depth != null) {
+ return DepthAwareNormalizedNodeWriter.forStreamWriter(xmlStreamWriter, depth);
+ }
+
+ return RestconfDelegatingNormalizedNodeWriter.forStreamWriter(xmlStreamWriter);
+ }
+
+ private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ final QName name = data.getIdentifier().getNodeType();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, name.getLocalName(),
+ name.getNamespace().toString());
+ xmlWriter.writeDefaultNamespace(name.getNamespace().toString());
+ for (final NormalizedNode child : data.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.writeEndElement();
+ xmlWriter.flush();
+ } catch (final XMLStreamException e) {
+ throw new IOException("Failed to write elements", e);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java
new file mode 100644
index 0000000..63586af
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchJsonBodyWriter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+
+
+@Provider
+@Produces({ Draft02.MediaTypes.PATCH_STATUS + RestconfService.JSON })
+public class PatchJsonBodyWriter implements MessageBodyWriter<PatchStatusContext> {
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return type.equals(PatchStatusContext.class);
+ }
+
+ @Override
+ public long getSize(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+ throws IOException, WebApplicationException {
+
+ final JsonWriter jsonWriter = createJsonWriter(entityStream);
+ jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status");
+ jsonWriter.beginObject();
+ jsonWriter.name("patch-id").value(patchStatusContext.getPatchId());
+ if (patchStatusContext.isOk()) {
+ reportSuccess(jsonWriter);
+ } else {
+ if (patchStatusContext.getGlobalErrors() != null) {
+ reportErrors(patchStatusContext.getGlobalErrors(), jsonWriter);
+ }
+
+ jsonWriter.name("edit-status");
+ jsonWriter.beginObject();
+ jsonWriter.name("edit");
+ jsonWriter.beginArray();
+ for (final PatchStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) {
+ jsonWriter.beginObject();
+ jsonWriter.name("edit-id").value(patchStatusEntity.getEditId());
+ if (patchStatusEntity.getEditErrors() != null) {
+ reportErrors(patchStatusEntity.getEditErrors(), jsonWriter);
+ } else {
+ if (patchStatusEntity.isOk()) {
+ reportSuccess(jsonWriter);
+ }
+ }
+ jsonWriter.endObject();
+ }
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ }
+ jsonWriter.endObject();
+ jsonWriter.endObject();
+ jsonWriter.flush();
+ }
+
+ private static void reportSuccess(final JsonWriter jsonWriter) throws IOException {
+ jsonWriter.name("ok").beginArray().nullValue().endArray();
+ }
+
+ private static void reportErrors(final List<RestconfError> errors, final JsonWriter jsonWriter) throws IOException {
+ jsonWriter.name("errors");
+ jsonWriter.beginObject();
+ jsonWriter.name("error");
+ jsonWriter.beginArray();
+
+ for (final RestconfError restconfError : errors) {
+ jsonWriter.beginObject();
+ jsonWriter.name("error-type").value(restconfError.getErrorType().elementBody());
+ jsonWriter.name("error-tag").value(restconfError.getErrorTag().elementBody());
+
+ // optional node
+ if (restconfError.getErrorPath() != null) {
+ jsonWriter.name("error-path").value(restconfError.getErrorPath().toString());
+ }
+
+ // optional node
+ if (restconfError.getErrorMessage() != null) {
+ jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+ }
+
+ // optional node
+ if (restconfError.getErrorInfo() != null) {
+ jsonWriter.name("error-info").value(restconfError.getErrorInfo());
+ }
+
+ jsonWriter.endObject();
+ }
+
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ }
+
+ private static JsonWriter createJsonWriter(final OutputStream entityStream) {
+ return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8));
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java
new file mode 100644
index 0000000..d941d6c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/PatchXmlBodyWriter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+
+@Provider
+@Produces({ Draft02.MediaTypes.PATCH_STATUS + RestconfService.XML })
+public class PatchXmlBodyWriter implements MessageBodyWriter<PatchStatusContext> {
+
+ private static final XMLOutputFactory XML_FACTORY;
+
+ static {
+ XML_FACTORY = XMLOutputFactory.newFactory();
+ XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
+
+ @Override
+ public boolean isWriteable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return type.equals(PatchStatusContext.class);
+ }
+
+ @Override
+ public long getSize(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+ throws WebApplicationException {
+
+ try {
+ final XMLStreamWriter xmlWriter =
+ XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
+ writeDocument(xmlWriter, patchStatusContext);
+ } catch (final XMLStreamException | FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void writeDocument(final XMLStreamWriter writer, final PatchStatusContext context)
+ throws XMLStreamException {
+ writer.writeStartElement("", "yang-patch-status", "urn:ietf:params:xml:ns:yang:ietf-yang-patch");
+ writer.writeStartElement("patch-id");
+ writer.writeCharacters(context.getPatchId());
+ writer.writeEndElement();
+
+ if (context.isOk()) {
+ writer.writeEmptyElement("ok");
+ } else {
+ if (context.getGlobalErrors() != null) {
+ reportErrors(context.getGlobalErrors(), writer);
+ }
+ writer.writeStartElement("edit-status");
+ for (final PatchStatusEntity patchStatusEntity : context.getEditCollection()) {
+ writer.writeStartElement("edit");
+ writer.writeStartElement("edit-id");
+ writer.writeCharacters(patchStatusEntity.getEditId());
+ writer.writeEndElement();
+ if (patchStatusEntity.getEditErrors() != null) {
+ reportErrors(patchStatusEntity.getEditErrors(), writer);
+ } else {
+ if (patchStatusEntity.isOk()) {
+ writer.writeEmptyElement("ok");
+ }
+ }
+ writer.writeEndElement();
+ }
+ writer.writeEndElement();
+
+ }
+ writer.writeEndElement();
+
+ writer.flush();
+ }
+
+ private static void reportErrors(final List<RestconfError> errors, final XMLStreamWriter writer)
+ throws XMLStreamException {
+ writer.writeStartElement("errors");
+
+ for (final RestconfError restconfError : errors) {
+ writer.writeStartElement("error-type");
+ writer.writeCharacters(restconfError.getErrorType().elementBody());
+ writer.writeEndElement();
+
+ writer.writeStartElement("error-tag");
+ writer.writeCharacters(restconfError.getErrorTag().elementBody());
+ writer.writeEndElement();
+
+ // optional node
+ if (restconfError.getErrorPath() != null) {
+ writer.writeStartElement("error-path");
+ writer.writeCharacters(restconfError.getErrorPath().toString());
+ writer.writeEndElement();
+ }
+
+ // optional node
+ if (restconfError.getErrorMessage() != null) {
+ writer.writeStartElement("error-message");
+ writer.writeCharacters(restconfError.getErrorMessage());
+ writer.writeEndElement();
+ }
+
+ // optional node
+ if (restconfError.getErrorInfo() != null) {
+ writer.writeStartElement("error-info");
+ writer.writeCharacters(restconfError.getErrorInfo());
+ writer.writeEndElement();
+ }
+ }
+
+ writer.writeEndElement();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java
new file mode 100644
index 0000000..d8918df
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfApplication.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Application;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContentYangBodyWriter;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContentYinBodyWriter;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalServiceImpl;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.StatisticsRestconfServiceWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+@Deprecated(since = "2.0.12")
+public class RestconfApplication extends Application {
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfApplication.class);
+
+ private final ControllerContext controllerContext;
+ private final StatisticsRestconfServiceWrapper statsServiceWrapper;
+
+ @Inject
+ public RestconfApplication(final ControllerContext controllerContext,
+ final StatisticsRestconfServiceWrapper statsServiceWrapper) {
+ this.controllerContext = controllerContext;
+ this.statsServiceWrapper = statsServiceWrapper;
+ LOG.warn("Pre-standard version of RESTCONF activated. Please note that this implementation is considered "
+ + "obsoleve and WILL BE REMOVED IN THE NEXT MAJOR RELEASE. Please use the RFC8040-compliant "
+ + "implementation instead.");
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return ImmutableSet.<Class<?>>builder()
+ .add(PatchJsonBodyWriter.class)
+ .add(PatchXmlBodyWriter.class)
+ .add(NormalizedNodeJsonBodyWriter.class)
+ .add(NormalizedNodeXmlBodyWriter.class)
+ .add(SchemaExportContentYinBodyWriter.class)
+ .add(SchemaExportContentYangBodyWriter.class)
+ .build();
+ }
+
+ @Override
+ public Set<Object> getSingletons() {
+ final Set<Object> singletons = new HashSet<>();
+ final SchemaRetrievalServiceImpl schemaRetrieval = new SchemaRetrievalServiceImpl(controllerContext);
+ singletons.add(schemaRetrieval);
+ singletons.add(new RestconfCompositeWrapper(statsServiceWrapper, schemaRetrieval));
+ singletons.add(new RestconfDocumentedExceptionMapper(controllerContext));
+ singletons.add(new XmlNormalizedNodeBodyReader(controllerContext));
+ singletons.add(new JsonNormalizedNodeBodyReader(controllerContext));
+ singletons.add(new XmlToPatchBodyReader(controllerContext));
+ singletons.add(new JsonToPatchBodyReader(controllerContext));
+// singletons.add(StructuredDataToXmlProvider.INSTANCE);
+// singletons.add(StructuredDataToJsonProvider.INSTANCE);
+// singletons.add(JsonToCompositeNodeProvider.INSTANCE);
+// singletons.add(XmlToCompositeNodeProvider.INSTANCE);
+ return singletons;
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java
new file mode 100644
index 0000000..90b83a0
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalService;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+
+public class RestconfCompositeWrapper implements RestconfService, SchemaRetrievalService {
+
+ private final RestconfService restconf;
+ private final SchemaRetrievalService schema;
+
+ public RestconfCompositeWrapper(final RestconfService restconf, final SchemaRetrievalService schema) {
+ this.restconf = requireNonNull(restconf);
+ this.schema = requireNonNull(schema);
+ }
+
+ @Override
+ public Object getRoot() {
+ return this.restconf.getRoot();
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ return this.restconf.getModules(uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getModules(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getModule(identifier, uriInfo);
+ }
+
+ @Override
+ public String getOperationsJSON() {
+ return this.restconf.getOperationsJSON();
+ }
+
+ @Override
+ public String getOperationsXML() {
+ return this.restconf.getOperationsXML();
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.getOperations(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.invokeRpc(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.readConfigurationData(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.readOperationalData(identifier, uriInfo);
+ }
+
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.updateConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.createConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.restconf.createConfigurationData(payload, uriInfo);
+ }
+
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ return this.restconf.deleteConfigurationData(identifier);
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ return this.restconf.subscribeToStream(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ return this.restconf.getAvailableStreams(uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext payload,
+ final UriInfo uriInfo) {
+ return this.restconf.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext context, final UriInfo uriInfo) {
+ return this.restconf.patchConfigurationData(context, uriInfo);
+ }
+
+ @Override
+ public SchemaExportContext getSchema(final String mountId) {
+ return this.schema.getSchema(mountId);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java
new file mode 100644
index 0000000..aaa1ccc
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import java.io.IOException;
+import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+
+/**
+ * This class just delegates all of the functionality to Yangtools normalized node writer.
+ */
+public final class RestconfDelegatingNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
+ private final NormalizedNodeWriter delegNNWriter;
+
+ private RestconfDelegatingNormalizedNodeWriter(final NormalizedNodeStreamWriter streamWriter, final boolean
+ orderKeyLeaves) {
+ this.delegNNWriter = NormalizedNodeWriter.forStreamWriter(streamWriter, orderKeyLeaves);
+ }
+
+ public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer) {
+ return forStreamWriter(writer, true);
+ }
+
+ public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
+ final boolean orderKeyLeaves) {
+ return new RestconfDelegatingNormalizedNodeWriter(writer, orderKeyLeaves);
+ }
+
+ @Override
+ public RestconfDelegatingNormalizedNodeWriter write(final NormalizedNode node) throws IOException {
+ delegNNWriter.write(node);
+ return this;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegNNWriter.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegNNWriter.close();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java
new file mode 100644
index 0000000..ff0be39
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfDocumentedExceptionMapper.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.gson.stream.JsonWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.ErrorTags;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+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.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
+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.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by resource implementations
+ * and translates appropriately to restconf error response as defined in the RESTCONF RFC draft.
+ *
+ * @author Thomas Pantelis
+ */
+@Provider
+public class RestconfDocumentedExceptionMapper implements ExceptionMapper<RestconfDocumentedException> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class);
+
+ private static final XMLOutputFactory XML_FACTORY;
+
+ static {
+ XML_FACTORY = XMLOutputFactory.newFactory();
+ XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
+
+ @Context
+ private HttpHeaders headers;
+
+ private final ControllerContext controllerContext;
+
+ public RestconfDocumentedExceptionMapper(final ControllerContext controllerContext) {
+ this.controllerContext = requireNonNull(controllerContext);
+ }
+
+ @Override
+ public Response toResponse(final RestconfDocumentedException exception) {
+
+ LOG.debug("In toResponse: {}", exception.getMessage());
+
+ final List<MediaType> mediaTypeList = new ArrayList<>();
+ if (headers.getMediaType() != null) {
+ mediaTypeList.add(headers.getMediaType());
+ }
+
+ mediaTypeList.addAll(headers.getAcceptableMediaTypes());
+ final MediaType mediaType = mediaTypeList.stream().filter(type -> !type.equals(MediaType.WILDCARD_TYPE))
+ .findFirst().orElse(MediaType.APPLICATION_JSON_TYPE);
+
+ LOG.debug("Using MediaType: {}", mediaType);
+
+ final List<RestconfError> errors = exception.getErrors();
+ if (errors.isEmpty()) {
+ // We don't actually want to send any content but, if we don't set any content here,
+ // the tomcat front-end will send back an html error report. To prevent that, set a
+ // single space char in the entity.
+
+ return Response.status(exception.getStatus()).type(MediaType.TEXT_PLAIN_TYPE).entity(" ").build();
+ }
+
+ final Status status = ErrorTags.statusOf(errors.iterator().next().getErrorTag());
+ final var errorsEntry = controllerContext.getRestconfModuleErrorsSchemaNode();
+ if (errorsEntry == null) {
+ return Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(exception.getMessage()).build();
+ }
+
+ final var errorsSchemaNode = errorsEntry.getValue();
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> errContBuild =
+ SchemaAwareBuilders.containerBuilder(errorsSchemaNode);
+
+ final var schemaList = ControllerContext.findInstanceDataChildrenByName(errorsSchemaNode,
+ Draft02.RestConfModule.ERROR_LIST_SCHEMA_NODE);
+ final DataSchemaNode errListSchemaNode = ControllerContext.getFirst(schemaList);
+ checkState(errListSchemaNode instanceof ListSchemaNode, "Found Error SchemaNode isn't ListSchemaNode");
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listErorsBuilder = SchemaAwareBuilders
+ .mapBuilder((ListSchemaNode) errListSchemaNode);
+
+
+ for (final RestconfError error : errors) {
+ listErorsBuilder.withChild(toErrorEntryNode(error, errListSchemaNode));
+ }
+ errContBuild.withChild(listErorsBuilder.build());
+
+ final NormalizedNodeContext errContext = new NormalizedNodeContext(
+ InstanceIdentifierContext.ofStack(errorsEntry.getKey(), null), errContBuild.build());
+
+ final String responseBody;
+ if (mediaType.getSubtype().endsWith("json")) {
+ responseBody = toJsonResponseBody(errContext);
+ } else {
+ responseBody = toXMLResponseBody(errContext);
+ }
+
+ return Response.status(status).type(mediaType).entity(responseBody).build();
+ }
+
+ private static MapEntryNode toErrorEntryNode(final RestconfError error, final DataSchemaNode errListSchemaNode) {
+ checkArgument(errListSchemaNode instanceof ListSchemaNode,
+ "errListSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) errListSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> errNodeValues = SchemaAwareBuilders
+ .mapEntryBuilder(listStreamSchemaNode);
+
+ var lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-type");
+ final DataSchemaNode errTypSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errTypSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTypSchemaNode)
+ .withValue(error.getErrorType().elementBody()).build());
+
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-tag");
+ final DataSchemaNode errTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errTagSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTagSchemaNode)
+ .withValue(error.getErrorTag().elementBody()).build());
+
+ if (error.getErrorAppTag() != null) {
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-app-tag");
+ final DataSchemaNode errAppTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errAppTagSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errAppTagSchemaNode)
+ .withValue(error.getErrorAppTag()).build());
+ }
+
+ lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+ listStreamSchemaNode, "error-message");
+ final DataSchemaNode errMsgSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+ checkState(errMsgSchemaNode instanceof LeafSchemaNode);
+ errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
+ .withValue(error.getErrorMessage()).build());
+
+ if (error.getErrorInfo() != null) {
+ // Oddly, error-info is defined as an empty container in the restconf yang. Apparently the
+ // intention is for implementors to define their own data content so we'll just treat it as a leaf
+ // with string data.
+ errNodeValues.withChild(ImmutableNodes.leafNode(Draft02.RestConfModule.ERROR_INFO_QNAME,
+ error.getErrorInfo()));
+ }
+
+ // TODO : find how could we add possible "error-path"
+
+ return errNodeValues.build();
+ }
+
+ private static String toJsonResponseBody(final NormalizedNodeContext errorsNode) {
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ NormalizedNode data = errorsNode.getData();
+ final InstanceIdentifierContext context = errorsNode.getInstanceIdentifierContext();
+ final DataSchemaNode schema = (DataSchemaNode) context.getSchemaNode();
+
+ final OutputStreamWriter outputWriter = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
+ if (data == null) {
+ throw new RestconfDocumentedException(Response.Status.NOT_FOUND);
+ }
+
+ final boolean isDataRoot;
+ final var stack = context.inference().toSchemaInferenceStack();
+ if (stack.isEmpty()) {
+ isDataRoot = true;
+ } else {
+ isDataRoot = false;
+ stack.exit();
+ // FIXME: Add proper handling of reading root.
+ }
+
+ XMLNamespace initialNs = null;
+ if (!schema.isAugmenting() && !(schema instanceof SchemaContext)) {
+ initialNs = schema.getQName().getNamespace();
+ }
+
+ final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter);
+ final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+ JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext()),
+ stack.toInference(), initialNs, jsonWriter);
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+ private boolean inOurLeaf;
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return jsonStreamWriter;
+ }
+
+ @Override
+ public void startLeafNode(final NodeIdentifier name) throws IOException {
+ if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ inOurLeaf = true;
+ jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName());
+ } else {
+ super.startLeafNode(name);
+ }
+ }
+
+ @Override
+ public void scalarValue(final Object value) throws IOException {
+ if (inOurLeaf) {
+ jsonWriter.value(value.toString());
+ } else {
+ super.scalarValue(value);
+ }
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ if (inOurLeaf) {
+ inOurLeaf = false;
+ } else {
+ super.endNode();
+ }
+ }
+ };
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
+ try {
+ if (isDataRoot) {
+ writeDataRoot(outputWriter,nnWriter,(ContainerNode) data);
+ } else {
+ if (data instanceof MapEntryNode) {
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .withChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ }
+ nnWriter.flush();
+ outputWriter.flush();
+ } catch (final IOException e) {
+ LOG.warn("Error writing error response body", e);
+ }
+
+ try {
+ streamWriter.close();
+ } catch (IOException e) {
+ LOG.warn("Failed to close stream writer", e);
+ }
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static String toXMLResponseBody(final NormalizedNodeContext errorsNode) {
+ final InstanceIdentifierContext pathContext = errorsNode.getInstanceIdentifierContext();
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+
+ final XMLStreamWriter xmlWriter;
+ try {
+ xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, StandardCharsets.UTF_8.name());
+ } catch (final XMLStreamException | FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+ NormalizedNode data = errorsNode.getData();
+
+ final boolean isDataRoot;
+ final var stack = pathContext.inference().toSchemaInferenceStack();
+ if (stack.isEmpty()) {
+ isDataRoot = true;
+ } else {
+ isDataRoot = false;
+ stack.exit();
+ }
+
+ final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+ stack.toInference());
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+ private boolean inOurLeaf;
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return xmlStreamWriter;
+ }
+
+ @Override
+ public void startLeafNode(final NodeIdentifier name) throws IOException {
+ if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ String ns = Draft02.RestConfModule.ERROR_INFO_QNAME.getNamespace().toString();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
+ Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName(), ns);
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ inOurLeaf = true;
+ } else {
+ super.startLeafNode(name);
+ }
+ }
+
+ @Override
+ public void scalarValue(final Object value) throws IOException {
+ if (inOurLeaf) {
+ try {
+ xmlWriter.writeCharacters(value.toString());
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ } else {
+ super.scalarValue(value);
+ }
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ if (inOurLeaf) {
+ try {
+ xmlWriter.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ inOurLeaf = false;
+ } else {
+ super.endNode();
+ }
+ }
+ };
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
+ try {
+ if (isDataRoot) {
+ writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
+ } else {
+ if (data instanceof MapEntryNode) {
+ // Restconf allows returning one list item. We need to wrap it
+ // in map node in order to serialize it properly
+ data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+ .addChild((MapEntryNode) data)
+ .build();
+ }
+ nnWriter.write(data);
+ nnWriter.flush();
+ }
+ } catch (final IOException e) {
+ LOG.warn("Error writing error response body.", e);
+ }
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ final QName name = SchemaContext.NAME;
+ try {
+ xmlWriter.writeStartElement(name.getNamespace().toString(), name.getLocalName());
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.writeEndElement();
+ xmlWriter.flush();
+ } catch (final XMLStreamException e) {
+ throw new IOException("Failed to write elements", e);
+ }
+ }
+
+ private static void writeDataRoot(final OutputStreamWriter outputWriter, final NormalizedNodeWriter nnWriter,
+ final ContainerNode data) throws IOException {
+ for (final DataContainerChild child : data.body()) {
+ nnWriter.write(child);
+ nnWriter.flush();
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
new file mode 100644
index 0000000..b61ed58
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Codec for module instance identifiers.
+ *
+ * @deprecated This class will be replaced by StringModuleInstanceIdentifierCodec from restconf-nb-rfc8040
+ */
+@Deprecated
+public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+
+ private final DataSchemaContextTree dataContextTree;
+ private final SchemaContext context;
+ private final String defaultPrefix;
+
+ public StringModuleInstanceIdentifierCodec(final EffectiveModelContext context) {
+ this.context = requireNonNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = "";
+ }
+
+ StringModuleInstanceIdentifierCodec(final EffectiveModelContext context, final @NonNull String defaultPrefix) {
+ this.context = requireNonNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = defaultPrefix;
+ }
+
+ @Override
+ protected Module moduleForPrefix(final String prefix) {
+ if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
+ return this.context.findModules(this.defaultPrefix).stream().findFirst().orElse(null);
+ } else {
+ return this.context.findModules(prefix).stream().findFirst().orElse(null);
+ }
+ }
+
+ @Override
+ protected DataSchemaContextTree getDataContextTree() {
+ return this.dataContextTree;
+ }
+
+ @Override
+ protected String prefixForNamespace(final XMLNamespace namespace) {
+ return this.context.findModules(namespace).stream().findFirst().map(Module::getName).orElse(null);
+ }
+} \ No newline at end of file
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java
new file mode 100644
index 0000000..9e21659
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/UnsupportedFormatException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+public class UnsupportedFormatException extends Exception {
+
+ private static final long serialVersionUID = -1741388894406313402L;
+
+ public UnsupportedFormatException() {
+ }
+
+ public UnsupportedFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnsupportedFormatException(String message) {
+ super(message);
+ }
+
+ public UnsupportedFormatException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java
new file mode 100644
index 0000000..1ad6985
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/WriterParameters.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+@Deprecated(forRemoval = true, since = "2.0.6")
+public final class WriterParameters {
+ static final WriterParameters EMPTY = new WriterParametersBuilder().build();
+
+ private final Integer depth;
+ private final boolean prettyPrint;
+
+ private WriterParameters(final WriterParametersBuilder builder) {
+ depth = builder.depth;
+ prettyPrint = builder.prettyPrint;
+ }
+
+ public Integer getDepth() {
+ return depth;
+ }
+
+ public boolean isPrettyPrint() {
+ return prettyPrint;
+ }
+
+ @Deprecated(forRemoval = true, since = "2.0.6")
+ public static final class WriterParametersBuilder {
+ private Integer depth;
+ private boolean prettyPrint;
+
+ public WriterParametersBuilder setDepth(final int depth) {
+ this.depth = depth;
+ return this;
+ }
+
+ public WriterParametersBuilder setPrettyPrint(final boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ return this;
+ }
+
+ public WriterParameters build() {
+ return new WriterParameters(this);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
new file mode 100644
index 0000000..84a7978
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+@Provider
+@Consumes({
+ Draft02.MediaTypes.DATA + RestconfService.XML,
+ Draft02.MediaTypes.OPERATION + RestconfService.XML,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML
+})
+public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsProvider
+ implements MessageBodyReader<NormalizedNodeContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
+
+ public XmlNormalizedNodeBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws
+ WebApplicationException {
+ try {
+ return readFrom(entityStream);
+ } catch (final RestconfDocumentedException e) {
+ throw e;
+ } catch (final Exception e) {
+ LOG.debug("Error parsing xml input", e);
+ RestconfDocumentedException.throwIfYangError(e);
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+ }
+
+ private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
+ XMLStreamException, ParserConfigurationException, URISyntaxException {
+ final InstanceIdentifierContext path = getInstanceIdentifierContext();
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ // represent empty nopayload input
+ return new NormalizedNodeContext(path, null);
+ }
+
+ final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
+ return parse(path, doc);
+ }
+
+ private NormalizedNodeContext parse(final InstanceIdentifierContext pathContext,final Document doc)
+ throws XMLStreamException, IOException, SAXException, URISyntaxException {
+ final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
+ DataSchemaNode schemaNode;
+ final List<PathArgument> iiToDataList = new ArrayList<>();
+ Inference inference;
+ if (schemaNodeContext instanceof RpcDefinition) {
+ schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
+ inference = pathContext.inference();
+ } else if (schemaNodeContext instanceof DataSchemaNode) {
+ schemaNode = (DataSchemaNode) schemaNodeContext;
+
+ final String docRootElm = doc.getDocumentElement().getLocalName();
+ final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
+
+ if (isPost()) {
+ final var context = pathContext.getSchemaContext();
+ final var it = context.findModuleStatements(docRootNamespace).iterator();
+ checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
+ final var qname = QName.create(it.next().localQNameModule(), docRootElm);
+
+ final var nodeAndStack = DataSchemaContextTree.from(context)
+ .enterPath(pathContext.getInstanceIdentifier()).orElseThrow();
+
+ final var stack = nodeAndStack.stack();
+ var current = nodeAndStack.node();
+ do {
+ final var next = current.enterChild(stack, qname);
+ checkState(next != null, "Child \"%s\" was not found in parent schema node \"%s\"", qname,
+ schemaNode);
+ iiToDataList.add(next.getIdentifier());
+ schemaNode = next.getDataSchemaNode();
+ current = next;
+ } while (current.isMixin());
+
+ // We need to unwind the last identifier if it a NodeIdentifierWithPredicates, as it does not have
+ // any predicates at all. The real identifier is then added below
+ if (stack.currentStatement() instanceof ListEffectiveStatement) {
+ iiToDataList.remove(iiToDataList.size() - 1);
+ }
+
+ inference = stack.toInference();
+
+ } else {
+ // PUT
+ final QName scQName = schemaNode.getQName();
+ checkState(docRootElm.equals(scQName.getLocalName()) && docRootNamespace.equals(scQName.getNamespace()),
+ "Not correct message root element \"%s\", should be \"%s\"", docRootElm, scQName);
+ inference = pathContext.inference();
+ }
+ } else {
+ throw new IllegalStateException("Unknown SchemaNode");
+ }
+
+
+ NormalizedNode parsed;
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ if (schemaNode instanceof ContainerLike || schemaNode instanceof ListSchemaNode
+ || schemaNode instanceof LeafSchemaNode) {
+ final XmlParserStream xmlParser = XmlParserStream.create(writer, inference);
+ xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
+ parsed = resultHolder.getResult();
+
+ // When parsing an XML source with a list root node
+ // the new XML parser always returns a MapNode with one MapEntryNode inside.
+ // However, the old XML parser returned a MapEntryNode directly in this place.
+ // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
+ if (parsed instanceof MapNode) {
+ final MapNode mapNode = (MapNode) parsed;
+ // extracting the MapEntryNode
+ parsed = mapNode.body().iterator().next();
+ }
+
+ if (schemaNode instanceof ListSchemaNode && isPost()) {
+ iiToDataList.add(parsed.getIdentifier());
+ }
+ } else {
+ LOG.warn("Unknown schema node extension {} was not parsed", schemaNode.getClass());
+ parsed = null;
+ }
+
+ return new NormalizedNodeContext(pathContext.withConcatenatedArgs(iiToDataList), parsed);
+ }
+}
+
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
new file mode 100644
index 0000000..9cc4766
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMSource;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Yang PATCH Reader for XML.
+ *
+ * @deprecated This class will be replaced by XmlToPatchBodyReader from restconf-nb-rfc8040
+ */
+@Deprecated
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.XML})
+public class XmlToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider implements
+ MessageBodyReader<PatchContext> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlToPatchBodyReader.class);
+
+ public XmlToPatchBodyReader(final ControllerContext controllerContext) {
+ super(controllerContext);
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType) {
+ return true;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchContext readFrom(final Class<PatchContext> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+ throws WebApplicationException {
+
+ try {
+ final InstanceIdentifierContext path = getInstanceIdentifierContext();
+ final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+ if (nonEmptyInputStreamOptional.isEmpty()) {
+ // represent empty nopayload input
+ return new PatchContext(path, null, null);
+ }
+
+ final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
+ return parse(path, doc);
+ } catch (final RestconfDocumentedException e) {
+ throw e;
+ } catch (final Exception e) {
+ LOG.debug("Error parsing xml input", e);
+ RestconfDocumentedException.throwIfYangError(e);
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+ }
+
+ private static PatchContext parse(final InstanceIdentifierContext pathContext, final Document doc)
+ throws XMLStreamException, IOException, ParserConfigurationException, SAXException, URISyntaxException {
+ final List<PatchEntity> resultCollection = new ArrayList<>();
+ final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
+ final NodeList editNodes = doc.getElementsByTagName("edit");
+
+ for (int i = 0; i < editNodes.getLength(); i++) {
+ DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
+ final Element element = (Element) editNodes.item(i);
+ final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue();
+ final PatchEditOperation oper = PatchEditOperation.valueOf(operation.toUpperCase(Locale.ROOT));
+
+ final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue();
+ final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue();
+ final List<Element> values = readValueNodes(element, oper);
+ final Element firstValueElement = values != null ? values.get(0) : null;
+
+ // get namespace according to schema node from path context or value
+ final String namespace = firstValueElement == null
+ ? schemaNode.getQName().getNamespace().toString() : firstValueElement.getNamespaceURI();
+
+ // find module according to namespace
+ final Module module = pathContext.getSchemaContext().findModules(XMLNamespace.of(namespace)).iterator()
+ .next();
+
+ // initialize codec + set default prefix derived from module name
+ final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+ pathContext.getSchemaContext(), module.getName());
+
+ // find complete path to target and target schema node
+ // target can be also empty (only slash)
+ YangInstanceIdentifier targetII;
+ final SchemaNode targetNode;
+ final Inference inference;
+ if (target.equals("/")) {
+ targetII = pathContext.getInstanceIdentifier();
+ targetNode = pathContext.getSchemaContext();
+ inference = pathContext.inference();
+ } else {
+ targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier())
+ .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement,
+ namespace,
+ module.getQNameModule().getRevision().map(Revision::toString).orElse(null))));
+ // move schema node
+ final var result = codec.getDataContextTree().enterPath(targetII).orElseThrow();
+ schemaNode = result.node().getDataSchemaNode();
+
+ final var stack = result.stack();
+ inference = stack.toInference();
+
+ stack.exit();
+ final EffectiveStatement<?, ?> parentStmt = stack.currentStatement();
+ verify(parentStmt instanceof SchemaNode, "Unexpected parent %s", parentStmt);
+ targetNode = (SchemaNode) parentStmt;
+ }
+
+ if (targetNode == null) {
+ LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode());
+ throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ if (oper.isWithValue()) {
+ final NormalizedNode parsed;
+ if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode) {
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+ final XmlParserStream xmlParser = XmlParserStream.create(writer, inference);
+ xmlParser.traverse(new DOMSource(firstValueElement));
+ parsed = resultHolder.getResult();
+ } else {
+ parsed = null;
+ }
+
+ // for lists allow to manipulate with list items through their parent
+ if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+ targetII = targetII.getParent();
+ }
+
+ resultCollection.add(new PatchEntity(editId, oper, targetII, parsed));
+ } else {
+ resultCollection.add(new PatchEntity(editId, oper, targetII));
+ }
+ }
+
+ return new PatchContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+ }
+
+ /**
+ * Read value nodes.
+ *
+ * @param element Element of current edit operation
+ * @param operation Name of current operation
+ * @return List of value elements
+ */
+ private static List<Element> readValueNodes(final @NonNull Element element,
+ final @NonNull PatchEditOperation operation) {
+ final Node valueNode = element.getElementsByTagName("value").item(0);
+
+ if (operation.isWithValue() && valueNode == null) {
+ throw new RestconfDocumentedException("Error parsing input",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ if (!operation.isWithValue() && valueNode != null) {
+ throw new RestconfDocumentedException("Error parsing input",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ if (valueNode == null) {
+ return null;
+ }
+
+ final List<Element> result = new ArrayList<>();
+ final NodeList childNodes = valueNode.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ if (childNodes.item(i) instanceof Element) {
+ result.add((Element) childNodes.item(i));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Prepare non-conditional XPath suitable for deserialization with {@link StringModuleInstanceIdentifierCodec}.
+ *
+ * @param schemaNode Top schema node
+ * @param target Edit operation target
+ * @param value Element with value
+ * @param namespace Module namespace
+ * @param revision Module revision
+ * @return Non-conditional XPath
+ */
+ private static String prepareNonCondXpath(final @NonNull DataSchemaNode schemaNode, final @NonNull String target,
+ final @NonNull Element value, final @NonNull String namespace, final @NonNull String revision) {
+ final Iterator<String> args = Splitter.on("/").split(target.substring(target.indexOf(':') + 1)).iterator();
+
+ final StringBuilder nonCondXpath = new StringBuilder();
+ SchemaNode childNode = schemaNode;
+
+ while (args.hasNext()) {
+ final String s = args.next();
+ nonCondXpath.append('/').append(s);
+ childNode = ((DataNodeContainer) childNode).getDataChildByName(QName.create(namespace, revision, s));
+
+ if (childNode instanceof ListSchemaNode && args.hasNext()) {
+ appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), args);
+ }
+ }
+
+ if (childNode instanceof ListSchemaNode && value != null) {
+ final Iterator<String> keyValues = readKeyValues(value,
+ ((ListSchemaNode) childNode).getKeyDefinition().iterator());
+ appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), keyValues);
+ }
+
+ return nonCondXpath.toString();
+ }
+
+ /**
+ * Read value for every list key.
+ *
+ * @param value Value element
+ * @param keys Iterator of list keys names
+ * @return Iterator of list keys values
+ */
+ private static Iterator<String> readKeyValues(final @NonNull Element value, final @NonNull Iterator<QName> keys) {
+ final List<String> result = new ArrayList<>();
+
+ while (keys.hasNext()) {
+ result.add(value.getElementsByTagName(keys.next().getLocalName()).item(0).getFirstChild().getNodeValue());
+ }
+
+ return result.iterator();
+ }
+
+ /**
+ * Append key name - key value pairs for every list key to {@code nonCondXpath}.
+ *
+ * @param nonCondXpath Builder for creating non-conditional XPath
+ * @param keyNames Iterator of list keys names
+ * @param keyValues Iterator of list keys values
+ */
+ private static void appendKeys(final @NonNull StringBuilder nonCondXpath, final @NonNull Iterator<QName> keyNames,
+ final @NonNull Iterator<String> keyValues) {
+ while (keyNames.hasNext()) {
+ nonCondXpath.append("[").append(keyNames.next().getLocalName()).append("='").append(keyValues.next())
+ .append("']");
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java
new file mode 100644
index 0000000..07eb8e2
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/rest/impl/package-info.java
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.rest.impl; \ No newline at end of file
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java
new file mode 100644
index 0000000..5e3426c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.api;
+
+import java.util.Optional;
+import javax.ws.rs.core.MultivaluedMap;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+
+/**
+ * Provides restconf CRUD operations via code with input/output data in JSON format.
+ *
+ * @author Thomas Pantelis.
+ */
+public interface JSONRestconfService {
+ /**
+ * The data tree root path.
+ */
+ String ROOT_PATH = null;
+
+ /**
+ * Issues a restconf PUT request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param payload the payload data in JSON format.
+ * @throws OperationFailedException if the request fails.
+ */
+ void put(String uriPath, @NonNull String payload) throws OperationFailedException;
+
+ /**
+ * Issues a restconf POST request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param payload the payload data in JSON format.
+ * @throws OperationFailedException if the request fails.
+ */
+ void post(String uriPath, @NonNull String payload) throws OperationFailedException;
+
+ /**
+ * Issues a restconf DELETE request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @throws OperationFailedException if the request fails.
+ */
+ void delete(String uriPath) throws OperationFailedException;
+
+ /**
+ * Issues a restconf GET request to the given data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param datastoreType the data store type to read from.
+ * @return an Optional containing the data in JSON format if present.
+ * @throws OperationFailedException if the request fails.
+ */
+ Optional<String> get(String uriPath, LogicalDatastoreType datastoreType)
+ throws OperationFailedException;
+
+ /**
+ * Invokes a yang-defined RPC.
+ *
+ * @param uriPath the path representing the RPC to invoke, eg "toaster:make-toast".
+ * @param input the input in JSON format if the RPC takes input.
+ * @return an Optional containing the output in JSON format if the RPC returns output.
+ * @throws OperationFailedException if the request fails.
+ */
+ Optional<String> invokeRpc(@NonNull String uriPath, Optional<String> input) throws OperationFailedException;
+
+ /**
+ * Issues a restconf PATCH request to the configuration data store.
+ *
+ * @param uriPath the yang instance identifier path, eg "opendaylight-inventory:nodes/node/device-id".
+ * To specify the root, use {@link ROOT_PATH}.
+ * @param payload the payload data in JSON format.
+ * @return an Optional containing the patch response data in JSON format.
+ * @throws OperationFailedException if the request fails.
+ */
+ Optional<String> patch(@NonNull String uriPath, @NonNull String payload) throws OperationFailedException;
+
+ /**
+ * Subscribe to a stream.
+ * @param identifier the identifier of the stream, e.g., "data-change-event-subscription/neutron:neutron/...
+ * ...neutron:ports/datastore=OPERATIONAL/scope=SUBTREE".
+ * @param params HTTP query parameters or null.
+ * @return On optional containing the JSON response.
+ * @throws OperationFailedException if the requests fails.
+ */
+ Optional<String> subscribeToStream(@NonNull String identifier, MultivaluedMap<String, String> params)
+ throws OperationFailedException;
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/RestConfConfig.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/RestConfConfig.java
new file mode 100644
index 0000000..169a69d
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/api/RestConfConfig.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.api;
+
+import java.net.InetAddress;
+
+/**
+ * Configuration for the RESTCONF server.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface RestConfConfig {
+
+ /**
+ * IP interface which the WebSocket server will listen on.
+ */
+ default InetAddress webSocketAddress() {
+ return InetAddress.getLoopbackAddress();
+ }
+
+ /**
+ * TCP port which the WebSocket server will listen on.
+ */
+ int webSocketPort();
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java
new file mode 100644
index 0000000..4efc6a8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BatchedExistenceCheck.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+final class BatchedExistenceCheck {
+ private static final AtomicIntegerFieldUpdater<BatchedExistenceCheck> UPDATER =
+ AtomicIntegerFieldUpdater.newUpdater(BatchedExistenceCheck.class, "outstanding");
+
+ private final SettableFuture<Entry<YangInstanceIdentifier, ReadFailedException>> future = SettableFuture.create();
+
+ @SuppressWarnings("unused")
+ private volatile int outstanding;
+
+ private BatchedExistenceCheck(final int total) {
+ this.outstanding = total;
+ }
+
+ static BatchedExistenceCheck start(final DOMDataTreeReadOperations readTx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier parentPath,
+ final Collection<? extends NormalizedNode> children) {
+ final BatchedExistenceCheck ret = new BatchedExistenceCheck(children.size());
+ for (NormalizedNode child : children) {
+ final YangInstanceIdentifier path = parentPath.node(child.getIdentifier());
+ readTx.exists(datastore, path).addCallback(new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(final Boolean result) {
+ ret.complete(path, result);
+ }
+
+ @Override
+ @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE")
+ public void onFailure(final Throwable throwable) {
+ final Exception e;
+ if (throwable instanceof Exception) {
+ e = (Exception) throwable;
+ } else {
+ e = new ExecutionException(throwable);
+ }
+
+ ret.complete(path, ReadFailedException.MAPPER.apply(e));
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ return ret;
+ }
+
+ Entry<YangInstanceIdentifier, ReadFailedException> getFailure() throws InterruptedException {
+ try {
+ return future.get();
+ } catch (ExecutionException e) {
+ // This should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void complete(final YangInstanceIdentifier childPath, final boolean present) {
+ final int count = UPDATER.decrementAndGet(this);
+ if (present) {
+ future.set(new SimpleImmutableEntry<>(childPath, null));
+ } else if (count == 0) {
+ future.set(null);
+ }
+ }
+
+ private void complete(final YangInstanceIdentifier childPath, final ReadFailedException cause) {
+ UPDATER.decrementAndGet(this);
+ future.set(new SimpleImmutableEntry<>(childPath, cause));
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java
new file mode 100644
index 0000000..18d19e4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/Bierman02RestConfWiring.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.servlet.ServletException;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMNotificationService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.netconf.sal.rest.impl.RestconfApplication;
+import org.opendaylight.netconf.sal.restconf.api.RestConfConfig;
+import org.opendaylight.netconf.sal.restconf.web.WebInitializer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Standalone wiring for RESTCONF.
+ *
+ * <p>ACK: Some lines here were originally inspired by the RestConfWiring class in
+ * opendaylight-simple which in turn was inspired by the CommunityRestConf class
+ * from lighty.io. The differences include (1) that this class is "pure Java"
+ * without depending on any binding framework utility classes; (2) we do not mix
+ * bierman02 and rfc8040 for proper modularity; (3) we simply use {@literal @}Inject
+ * instead of manual object wiring, where possible.
+ *
+ * @author Michael Vorburger.ch (see ACK note for history)
+ */
+@SuppressWarnings("deprecation")
+// NOT @Singleton, to avoid that the blueprint-maven-plugin generates <bean>, which we don't want for this
+public class Bierman02RestConfWiring {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Bierman02RestConfWiring.class);
+
+ private final RestconfProviderImpl webSocketServer;
+
+ @Inject
+ // The point of all the arguments here is simply to make your chosen Dependency Injection (DI) framework init. them
+ public Bierman02RestConfWiring(final RestConfConfig config,
+ final DOMSchemaService domSchemaService, final DOMMountPointService domMountPointService,
+ final DOMRpcService domRpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotificationService, final ControllerContext controllerContext,
+ final RestconfApplication application, final BrokerFacade broker, final RestconfImpl restconf,
+ final StatisticsRestconfServiceWrapper stats, final JSONRestconfServiceImpl jsonRestconfServiceImpl,
+ final WebInitializer webInitializer) {
+
+ // WebSocket
+ LOG.info("webSocketAddress = {}, webSocketPort = {}", config.webSocketAddress(), config.webSocketPort());
+ IpAddress wsIpAddress = IpAddressBuilder.getDefaultInstance(config.webSocketAddress().getHostAddress());
+ this.webSocketServer = new RestconfProviderImpl(stats, wsIpAddress,
+ new PortNumber(Uint16.valueOf(config.webSocketPort())));
+ }
+
+ @PostConstruct
+ public void start() throws ServletException {
+ this.webSocketServer.start();
+ }
+
+ @PreDestroy
+ public void stop() {
+ this.webSocketServer.close();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
new file mode 100644
index 0000000..ca867cb
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMNotificationListener;
+import org.opendaylight.mdsal.dom.api.DOMNotificationService;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class BrokerFacade implements Closeable {
+ private static final Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
+
+ private final ThreadLocal<Boolean> isMounted = new ThreadLocal<>();
+ private final DOMNotificationService domNotification;
+ private final ControllerContext controllerContext;
+ private final DOMDataBroker domDataBroker;
+
+ private volatile DOMRpcService rpcService;
+
+ @Inject
+ public BrokerFacade(final DOMRpcService rpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotification, final ControllerContext controllerContext) {
+ this.rpcService = requireNonNull(rpcService);
+ this.domDataBroker = requireNonNull(domDataBroker);
+ this.domNotification = requireNonNull(domNotification);
+ this.controllerContext = requireNonNull(controllerContext);
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use
+ * {@link #BrokerFacade(DOMRpcService, DOMDataBroker, DOMNotificationService, ControllerContext)}
+ * constructor instead.
+ */
+ @Deprecated
+ public static BrokerFacade newInstance(final DOMRpcService rpcService, final DOMDataBroker domDataBroker,
+ final DOMNotificationService domNotification, final ControllerContext controllerContext) {
+ return new BrokerFacade(rpcService, domDataBroker, domNotification, controllerContext);
+ }
+
+ @Override
+ @PreDestroy
+ public void close() {
+ }
+
+ /**
+ * Read config data by path.
+ *
+ * @param path
+ * path of data
+ * @return read date
+ */
+ public NormalizedNode readConfigurationData(final YangInstanceIdentifier path) {
+ return readConfigurationData(path, null);
+ }
+
+ /**
+ * Read config data by path.
+ *
+ * @param path
+ * path of data
+ * @param withDefa
+ * value of with-defaults parameter
+ * @return read date
+ */
+ public NormalizedNode readConfigurationData(final YangInstanceIdentifier path, final String withDefa) {
+ try (DOMDataTreeReadTransaction tx = domDataBroker.newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
+ }
+ }
+
+ /**
+ * Read config data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ return readConfigurationData(mountPoint, path, null);
+ }
+
+ /**
+ * Read config data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @param withDefa
+ * value of with-defaults parameter
+ * @return read data
+ */
+ public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path,
+ final String withDefa) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ try (DOMDataTreeReadTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, CONFIGURATION, path, withDefa);
+ }
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ /**
+ * Read operational data by path.
+ *
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readOperationalData(final YangInstanceIdentifier path) {
+ try (DOMDataTreeReadTransaction tx = domDataBroker.newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, OPERATIONAL, path);
+ }
+ }
+
+ /**
+ * Read operational data from mount point by path.
+ *
+ * @param mountPoint
+ * mount point for reading data
+ * @param path
+ * path of data
+ * @return read data
+ */
+ public NormalizedNode readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ try (DOMDataTreeReadTransaction tx = domDataBrokerService.get().newReadOnlyTransaction()) {
+ return readDataViaTransaction(tx, OPERATIONAL, path);
+ }
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ /**
+ * <b>PUT configuration data</b>
+ *
+ * <p>
+ * Prepare result(status) for PUT operation and PUT data via transaction.
+ * Return wrapped status and future from PUT.
+ *
+ * @param globalSchema
+ * used by merge parents (if contains list)
+ * @param path
+ * path of node
+ * @param payload
+ * input data
+ * @param point
+ * point
+ * @param insert
+ * insert
+ * @return wrapper of status and future of PUT
+ */
+ public PutResult commitConfigurationDataPut(final EffectiveModelContext globalSchema,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final String insert, final String point) {
+ requireNonNull(globalSchema);
+ requireNonNull(path);
+ requireNonNull(payload);
+
+ isMounted.set(false);
+ final DOMDataTreeReadWriteTransaction newReadWriteTransaction = domDataBroker.newReadWriteTransaction();
+ final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
+ : Status.CREATED;
+ final FluentFuture<? extends CommitInfo> future = putDataViaTransaction(
+ newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema, insert, point);
+ isMounted.remove();
+ return new PutResult(status, future);
+ }
+
+ /**
+ * <b>PUT configuration data (Mount point)</b>
+ *
+ * <p>
+ * Prepare result(status) for PUT operation and PUT data via transaction.
+ * Return wrapped status and future from PUT.
+ *
+ * @param mountPoint
+ * mount point for getting transaction for operation and schema
+ * context for merging parents(if contains list)
+ * @param path
+ * path of node
+ * @param payload
+ * input data
+ * @param point
+ * point
+ * @param insert
+ * insert
+ * @return wrapper of status and future of PUT
+ */
+ public PutResult commitMountPointDataPut(final DOMMountPoint mountPoint, final YangInstanceIdentifier path,
+ final NormalizedNode payload, final String insert, final String point) {
+ requireNonNull(mountPoint);
+ requireNonNull(path);
+ requireNonNull(payload);
+
+ isMounted.set(true);
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ final DOMDataTreeReadWriteTransaction newReadWriteTransaction =
+ domDataBrokerService.get().newReadWriteTransaction();
+ final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null
+ ? Status.OK : Status.CREATED;
+ final FluentFuture<? extends CommitInfo> future = putDataViaTransaction(
+ newReadWriteTransaction, CONFIGURATION, path, payload, modelContext(mountPoint), insert, point);
+ isMounted.remove();
+ return new PutResult(status, future);
+ }
+ isMounted.remove();
+ throw dataBrokerUnavailable(path);
+ }
+
+ public PatchStatusContext patchConfigurationDataWithinTransaction(final PatchContext patchContext)
+ throws Exception {
+ final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
+
+ // get new transaction and schema context on server or on mounted device
+ final EffectiveModelContext schemaContext;
+ final DOMDataTreeReadWriteTransaction patchTransaction;
+ if (mountPoint == null) {
+ schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
+ patchTransaction = domDataBroker.newReadWriteTransaction();
+ } else {
+ schemaContext = modelContext(mountPoint);
+
+ final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
+
+ if (optional.isPresent()) {
+ patchTransaction = optional.get().newReadWriteTransaction();
+ } else {
+ // if mount point does not have broker it is not possible to continue and global error is reported
+ LOG.error("Http Patch {} has failed - device {} does not support broker service",
+ patchContext.getPatchId(), mountPoint.getIdentifier());
+ return new PatchStatusContext(
+ patchContext.getPatchId(),
+ null,
+ false,
+ ImmutableList.of(new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
+ "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier()))
+ );
+ }
+ }
+
+ final List<PatchStatusEntity> editCollection = new ArrayList<>();
+ List<RestconfError> editErrors;
+ boolean withoutError = true;
+
+ for (final PatchEntity patchEntity : patchContext.getData()) {
+ final PatchEditOperation operation = patchEntity.getOperation();
+ switch (operation) {
+ case CREATE:
+ if (withoutError) {
+ try {
+ postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case REPLACE:
+ if (withoutError) {
+ try {
+ putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode(), patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case DELETE:
+ case REMOVE:
+ if (withoutError) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ case MERGE:
+ if (withoutError) {
+ try {
+ mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), schemaContext);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ LOG.error("Error call http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), false, editErrors));
+ withoutError = false;
+ }
+ }
+ break;
+ default:
+ LOG.error("Unsupported http Patch operation {} on target {}",
+ operation,
+ patchEntity.getTargetNode().toString());
+ break;
+ }
+ }
+
+ // if errors then cancel transaction and return error status
+ if (!withoutError) {
+ patchTransaction.cancel();
+ return new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
+ }
+
+ // if no errors commit transaction
+ final CountDownLatch waiter = new CountDownLatch(1);
+ final FluentFuture<? extends CommitInfo> future = patchTransaction.commit();
+ final PatchStatusContextHelper status = new PatchStatusContextHelper();
+
+ future.addCallback(new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+ true, null));
+ waiter.countDown();
+ }
+
+ @Override
+ public void onFailure(final Throwable throwable) {
+ // if commit failed it is global error
+ LOG.error("Http Patch {} transaction commit has failed", patchContext.getPatchId());
+ status.setStatus(new PatchStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+ false, ImmutableList.of(
+ new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, throwable.getMessage()))));
+ waiter.countDown();
+ }
+ }, MoreExecutors.directExecutor());
+
+ waiter.await();
+ return status.getStatus();
+ }
+
+ // POST configuration
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataPost(
+ final EffectiveModelContext globalSchema, final YangInstanceIdentifier path,
+ final NormalizedNode payload, final String insert, final String point) {
+ isMounted.set(false);
+ FluentFuture<? extends CommitInfo> future =
+ postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload,
+ globalSchema, insert, point);
+ isMounted.remove();
+ return future;
+ }
+
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataPost(
+ final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final String insert, final String point) {
+ isMounted.set(true);
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ FluentFuture<? extends CommitInfo> future =
+ postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
+ payload, modelContext(mountPoint), insert, point);
+ isMounted.remove();
+ return future;
+ }
+ isMounted.remove();
+ throw dataBrokerUnavailable(path);
+ }
+
+ // DELETE configuration
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataDelete(final YangInstanceIdentifier path) {
+ return deleteDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
+ }
+
+ public FluentFuture<? extends CommitInfo> commitConfigurationDataDelete(
+ final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
+ }
+ throw dataBrokerUnavailable(path);
+ }
+
+ // RPC
+ public ListenableFuture<? extends DOMRpcResult> invokeRpc(final @NonNull QName type,
+ final @NonNull NormalizedNode input) {
+ if (rpcService == null) {
+ throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
+ }
+ LOG.trace("Invoke RPC {} with input: {}", type, input);
+ return rpcService.invokeRpc(type, input);
+ }
+
+ public void registerToListenDataChanges(final LogicalDatastoreType datastore, final Scope scope,
+ final ListenerAdapter listener) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final YangInstanceIdentifier path = listener.getPath();
+ DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
+ .getInstance(DOMDataTreeChangeService.class);
+ if (changeService == null) {
+ throw new UnsupportedOperationException("DOMDataBroker does not support the DOMDataTreeChangeService"
+ + domDataBroker);
+ }
+ DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(datastore, path);
+ ListenerRegistration<ListenerAdapter> registration =
+ changeService.registerDataTreeChangeListener(root, listener);
+ listener.setRegistration(registration);
+ }
+
+ private NormalizedNode readDataViaTransaction(final DOMDataTreeReadOperations transaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
+ return readDataViaTransaction(transaction, datastore, path, null);
+ }
+
+ private NormalizedNode readDataViaTransaction(final DOMDataTreeReadOperations transaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final String withDefa) {
+ LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
+
+ try {
+ final Optional<NormalizedNode> optional = transaction.read(datastore, path).get();
+ return optional.map(normalizedNode -> withDefa == null ? normalizedNode :
+ prepareDataByParamWithDef(normalizedNode, path, withDefa)).orElse(null);
+ } catch (InterruptedException e) {
+ LOG.warn("Error reading {} from datastore {}", path, datastore.name(), e);
+ throw new RestconfDocumentedException("Error reading data.", e);
+ } catch (ExecutionException e) {
+ LOG.warn("Error reading {} from datastore {}", path, datastore.name(), e);
+ throw RestconfDocumentedException.decodeAndThrow("Error reading data.", Throwables.getCauseAs(e,
+ ReadFailedException.class));
+ }
+ }
+
+ private NormalizedNode prepareDataByParamWithDef(final NormalizedNode result,
+ final YangInstanceIdentifier path, final String withDefa) {
+ boolean trim;
+ switch (withDefa) {
+ case "trim":
+ trim = true;
+ break;
+ case "explicit":
+ trim = false;
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad value used with with-defaults parameter : " + withDefa);
+ }
+
+ final EffectiveModelContext ctx = controllerContext.getGlobalSchema();
+ final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
+ final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (result instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) baseSchemaNode);
+ buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim);
+ return builder.build();
+ }
+
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder =
+ SchemaAwareBuilders.mapEntryBuilder((ListSchemaNode) baseSchemaNode);
+ buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) baseSchemaNode).getKeyDefinition());
+ return builder.build();
+ }
+
+ private void buildMapEntryBuilder(
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
+ final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim, final List<QName> keys) {
+ for (final DataContainerChild child : result.body()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> childBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim);
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> childBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
+ final Object nodeVal = child.body();
+ final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ SchemaAwareBuilders.leafBuilder((LeafSchemaNode) childSchema);
+ if (keys.contains(child.getIdentifier().getNodeType())) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ } else {
+ if (trim) {
+ if (defaultVal == null || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if (defaultVal != null && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void buildList(final CollectionNodeBuilder<MapEntryNode, SystemMapNode> builder, final MapNode result,
+ final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim,
+ final List<QName> keys) {
+ for (final MapEntryNode mapEntryNode : result.body()) {
+ final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(actualNode).orElseThrow()
+ .getDataSchemaNode();
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
+ SchemaAwareBuilders.mapEntryBuilder((ListSchemaNode) childSchema);
+ buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys);
+ builder.withChild(mapEntryBuilder.build());
+ }
+ }
+
+ private void buildCont(final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builder,
+ final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim) {
+ for (final DataContainerChild child : result.body()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.findChild(path).orElseThrow().getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> builderChild =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim);
+ builder.withChild(builderChild.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> childBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final Object defaultVal = ((LeafSchemaNode) childSchema).getType().getDefaultValue().orElse(null);
+ final Object nodeVal = child.body();
+ final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ SchemaAwareBuilders.leafBuilder((LeafSchemaNode) childSchema);
+ if (trim) {
+ if (defaultVal == null || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if (defaultVal != null && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(child.body());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * POST data and submit transaction {@link DOMDataReadWriteTransaction}.
+ */
+ private FluentFuture<? extends CommitInfo> postDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ postData(rwTransaction, datastore, path, payload, schemaContext, insert, point);
+ return rwTransaction.commit();
+ }
+
+ /**
+ * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}.
+ */
+ private void postDataWithinTransaction(
+ final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("POST {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ postData(rwTransaction, datastore, path, payload, schemaContext, null, null);
+ }
+
+ private void postData(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ if (insert == null) {
+ makeNormalPost(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readList,
+ schemaContext);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ makeNormalPost(rwTransaction, datastore, path.getParent().getParent(), readLeafList,
+ schemaContext);
+ }
+ }
+ break;
+ case "last":
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ break;
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList,
+ true);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, true);
+ }
+ }
+ break;
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList =
+ (UserMapNode) this.readConfigurationData(path.getParent().getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList,
+ false);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ } else {
+ insertWithPointLeafListPost(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, false);
+ }
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, "
+ + "but was: " + insert);
+ }
+ }
+
+ private void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String point, final UserLeafSetNode<?> readLeafList,
+ final boolean before) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int lastItemPosition = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
+ checkItemDoesNotExists(rwTransaction, datastore, childPath);
+ rwTransaction.put(datastore, childPath, nodeChild);
+ lastInsertedPosition++;
+ }
+ }
+
+ private void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final EffectiveModelContext schemaContext,
+ final String point, final MapNode readList, final boolean before) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int lastItemPosition = 0;
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (mapEntryNode.getIdentifier()
+ .equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent().getParent());
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
+ checkItemDoesNotExists(rwTransaction, datastore, childPath);
+ rwTransaction.put(datastore, childPath, mapEntryNode);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static DataSchemaNode checkListAndOrderedType(final EffectiveModelContext ctx,
+ final YangInstanceIdentifier path) {
+ final YangInstanceIdentifier parent = path.getParent();
+ final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).findChild(parent).orElseThrow();
+ final DataSchemaNode dataSchemaNode = node.getDataSchemaNode();
+
+ if (dataSchemaNode instanceof ListSchemaNode) {
+ if (!((ListSchemaNode) dataSchemaNode).isUserOrdered()) {
+ throw new RestconfDocumentedException("Insert parameter can be used only with ordered-by user list.");
+ }
+ return dataSchemaNode;
+ }
+ if (dataSchemaNode instanceof LeafListSchemaNode) {
+ if (!((LeafListSchemaNode) dataSchemaNode).isUserOrdered()) {
+ throw new RestconfDocumentedException(
+ "Insert parameter can be used only with ordered-by user leaf-list.");
+ }
+ return dataSchemaNode;
+ }
+ throw new RestconfDocumentedException("Insert parameter can be used only with list or leaf-list");
+ }
+
+ private void makeNormalPost(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ final Collection<? extends NormalizedNode> children;
+ if (payload instanceof MapNode) {
+ children = ((MapNode) payload).body();
+ } else if (payload instanceof LeafSetNode) {
+ children = ((LeafSetNode<?>) payload).body();
+ } else {
+ simplePostPut(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ if (children.isEmpty()) {
+ if (isMounted != null && !isMounted.get()) {
+
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()),
+ emptySubtree);
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ return;
+ }
+
+ // Kick off batch existence check first...
+ final BatchedExistenceCheck check = BatchedExistenceCheck.start(rwTransaction, datastore, path, children);
+
+ // ... now enqueue modifications. This relies on proper ordering of requests, i.e. these will not affect the
+ // result of the existence checks...
+ if (isMounted != null && !isMounted.get()) {
+
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ for (final NormalizedNode child : children) {
+ // FIXME: we really want a create(YangInstanceIdentifier, NormalizedNode) method in the transaction,
+ // as that would allow us to skip the existence checks
+ rwTransaction.put(datastore, path.node(child.getIdentifier()), child);
+ }
+
+ // ... finally collect existence checks and abort the transaction if any of them failed.
+ final Entry<YangInstanceIdentifier, ReadFailedException> failure;
+ try {
+ failure = check.getFailure();
+ } catch (InterruptedException e) {
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
+ }
+
+ if (failure != null) {
+ rwTransaction.cancel();
+ final ReadFailedException e = failure.getValue();
+ if (e == null) {
+ throw new RestconfDocumentedException("Data already exists for path: " + failure.getKey(),
+ ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS);
+ }
+
+ throw new RestconfDocumentedException("Could not determine the existence of path " + failure.getKey(), e,
+ e.getErrorList());
+ }
+ }
+
+ private void simplePostPut(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ if (isMounted != null && !isMounted.get()) {
+ ensureParentsByMerge(datastore, path, rwTransaction, schemaContext);
+ }
+ rwTransaction.put(datastore, path, payload);
+ }
+
+ private static boolean doesItemExist(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ try {
+ return rwTransaction.exists(store, path).get();
+ } catch (InterruptedException e) {
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Could not determine the existence of path " + path, e);
+ } catch (ExecutionException e) {
+ rwTransaction.cancel();
+ throw RestconfDocumentedException.decodeAndThrow("Could not determine the existence of path " + path,
+ Throwables.getCauseAs(e, ReadFailedException.class));
+ }
+ }
+
+ /**
+ * Check if item already exists. Throws error if it does NOT already exist.
+ * @param rwTransaction Current transaction
+ * @param store Used datastore
+ * @param path Path to item to verify its existence
+ */
+ private static void checkItemExists(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ if (!doesItemExist(rwTransaction, store, path)) {
+ LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path);
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ }
+ }
+
+ /**
+ * Check if item does NOT already exist. Throws error if it already exists.
+ * @param rwTransaction Current transaction
+ * @param store Used datastore
+ * @param path Path to item to verify its existence
+ */
+ private static void checkItemDoesNotExists(final DOMDataTreeReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ if (doesItemExist(rwTransaction, store, path)) {
+ LOG.trace("Operation via Restconf was not executed because data at {} already exists", path);
+ rwTransaction.cancel();
+ throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS);
+ }
+ }
+
+ /**
+ * PUT data and submit {@link DOMDataReadWriteTransaction}.
+ *
+ * @param point
+ * point
+ * @param insert
+ * insert
+ */
+ private FluentFuture<? extends CommitInfo> putDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+ putData(readWriteTransaction, datastore, path, payload, schemaContext, insert, point);
+ return readWriteTransaction.commit();
+ }
+
+ /**
+ * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}.
+ */
+ private void putDataWithinTransaction(
+ final DOMDataTreeReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("Put {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ putData(writeTransaction, datastore, path, payload, schemaContext, null, null);
+ }
+
+ // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
+ private void putData(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String insert, final String point) {
+ if (insert == null) {
+ makePut(rwTransaction, datastore, path, payload, schemaContext);
+ return;
+ }
+
+ final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
+ checkItemDoesNotExists(rwTransaction, datastore, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ makePut(rwTransaction, datastore, path.getParent(), readList, schemaContext);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ rwTransaction.delete(datastore, path.getParent());
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ makePut(rwTransaction, datastore, path.getParent(), readLeafList,
+ schemaContext);
+ }
+ }
+ break;
+ case "last":
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ break;
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList, true);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, true);
+ }
+ }
+ break;
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final UserMapNode readList = (UserMapNode) this.readConfigurationData(path.getParent());
+ if (readList == null || readList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readList, false);
+ }
+ } else {
+ final UserLeafSetNode<?> readLeafList =
+ (UserLeafSetNode<?>) readConfigurationData(path.getParent());
+ if (readLeafList == null || readLeafList.isEmpty()) {
+ simplePut(datastore, path, rwTransaction, schemaContext, payload);
+ } else {
+ insertWithPointLeafListPut(rwTransaction, datastore, path, payload, schemaContext, point,
+ readLeafList, false);
+ }
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, but was: "
+ + insert);
+ }
+ }
+
+ private void insertWithPointLeafListPut(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext, final String point, final UserLeafSetNode<?> readLeafList,
+ final boolean before) {
+ tx.delete(datastore, path.getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int index1 = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ index1++;
+ }
+ if (!before) {
+ index1++;
+ }
+ int index2 = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.body()) {
+ if (index2 == index1) {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
+ tx.put(datastore, childPath, nodeChild);
+ index2++;
+ }
+ }
+
+ private void insertWithPointListPut(final DOMDataTreeWriteTransaction tx, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload, final EffectiveModelContext schemaContext,
+ final String point, final UserMapNode readList, final boolean before) {
+ tx.delete(datastore, path.getParent());
+ final InstanceIdentifierContext instanceIdentifier = controllerContext.toInstanceIdentifier(point);
+ int index1 = 0;
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ index1++;
+ }
+ if (!before) {
+ index1++;
+ }
+ int index2 = 0;
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final MapEntryNode mapEntryNode : readList.body()) {
+ if (index2 == index1) {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
+ tx.put(datastore, childPath, mapEntryNode);
+ index2++;
+ }
+ }
+
+ private void makePut(final DOMDataTreeWriteTransaction tx, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ if (payload instanceof MapNode) {
+ final NormalizedNode emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ if (isMounted != null && !isMounted.get()) {
+ tx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+ }
+ for (final MapEntryNode child : ((MapNode) payload).body()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ tx.put(datastore, childPath, child);
+ }
+ } else {
+ simplePut(datastore, path, tx, schemaContext, payload);
+ }
+ }
+
+ private void simplePut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final DOMDataTreeWriteTransaction tx, final EffectiveModelContext schemaContext,
+ final NormalizedNode payload) {
+ if (isMounted != null && !isMounted.get()) {
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+ }
+ tx.put(datastore, path, payload);
+ }
+
+ private static FluentFuture<? extends CommitInfo> deleteDataViaTransaction(
+ final DOMDataTreeReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
+ checkItemExists(readWriteTransaction, datastore, path);
+ readWriteTransaction.delete(datastore, path);
+ return readWriteTransaction.commit();
+ }
+
+ private static void deleteDataWithinTransaction(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} within Restconf Patch: {}", datastore.name(), path);
+ tx.delete(datastore, path);
+ }
+
+ private static void mergeDataWithinTransaction(final DOMDataTreeWriteTransaction tx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload,
+ final EffectiveModelContext schemaContext) {
+ LOG.trace("Merge {} within Restconf Patch: {} with payload {}", datastore.name(), path, payload);
+ ensureParentsByMerge(datastore, path, tx, schemaContext);
+
+ // Since YANG Patch provides the option to specify what kind of operation for each edit,
+ // OpenDaylight should not change it.
+ tx.merge(datastore, path, payload);
+ }
+
+ public void registerToListenNotification(final NotificationListenerAdapter listener) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final ListenerRegistration<DOMNotificationListener> registration = domNotification
+ .registerNotificationListener(listener, listener.getSchemaPath());
+
+ listener.setRegistration(registration);
+ }
+
+ private static void ensureParentsByMerge(final LogicalDatastoreType store,
+ final YangInstanceIdentifier normalizedPath, final DOMDataTreeWriteTransaction tx,
+ final EffectiveModelContext schemaContext) {
+ final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
+ YangInstanceIdentifier rootNormalizedPath = null;
+
+ final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
+
+ while (it.hasNext()) {
+ final PathArgument pathArgument = it.next();
+ if (rootNormalizedPath == null) {
+ rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
+ }
+
+ if (it.hasNext()) {
+ normalizedPathWithoutChildArgs.add(pathArgument);
+ }
+ }
+
+ if (normalizedPathWithoutChildArgs.isEmpty()) {
+ return;
+ }
+
+ checkArgument(rootNormalizedPath != null, "Empty path received");
+ tx.merge(store, rootNormalizedPath, ImmutableNodes.fromInstanceId(schemaContext,
+ YangInstanceIdentifier.create(normalizedPathWithoutChildArgs)));
+ }
+
+ private static RestconfDocumentedException dataBrokerUnavailable(final YangInstanceIdentifier path) {
+ LOG.warn("DOM data broker service is not available for mount point {}", path);
+ return new RestconfDocumentedException("DOM data broker service is not available for mount point " + path);
+ }
+
+ private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+
+ private static final class PatchStatusContextHelper {
+ PatchStatusContext status;
+
+ public PatchStatusContext getStatus() {
+ return status;
+ }
+
+ public void setStatus(final PatchStatusContext status) {
+ this.status = status;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java
new file mode 100644
index 0000000..8e05bf1
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/ControllerContext.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.Closeable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response.Status;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.Draft02.RestConfModule;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public final class ControllerContext implements EffectiveModelContextListener, Closeable {
+ // FIXME: this should be in md-sal somewhere
+ public static final String MOUNT = "yang-ext:mount";
+
+ private static final Logger LOG = LoggerFactory.getLogger(ControllerContext.class);
+
+ private static final String NULL_VALUE = "null";
+
+ private static final String MOUNT_MODULE = "yang-ext";
+
+ private static final String MOUNT_NODE = "mount";
+
+ private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+
+ private final AtomicReference<Map<QName, RpcDefinition>> qnameToRpc = new AtomicReference<>(Collections.emptyMap());
+
+ private final DOMMountPointService mountService;
+ private final DOMYangTextSourceProvider yangTextSourceProvider;
+ private final ListenerRegistration<?> listenerRegistration;
+ private volatile EffectiveModelContext globalSchema;
+ private volatile DataNormalizer dataNormalizer;
+
+ @Inject
+ public ControllerContext(final DOMSchemaService schemaService, final DOMMountPointService mountService,
+ final DOMSchemaService domSchemaService) {
+ this.mountService = mountService;
+ yangTextSourceProvider = domSchemaService.getExtensions().getInstance(DOMYangTextSourceProvider.class);
+
+ onModelContextUpdated(schemaService.getGlobalContext());
+ listenerRegistration = schemaService.registerSchemaContextListener(this);
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use the
+ * {@link #ControllerContext(DOMSchemaService, DOMMountPointService, DOMSchemaService)}
+ * constructor instead.
+ */
+ @Deprecated
+ public static ControllerContext newInstance(final DOMSchemaService schemaService,
+ final DOMMountPointService mountService, final DOMSchemaService domSchemaService) {
+ return new ControllerContext(schemaService, mountService, domSchemaService);
+ }
+
+ private void setGlobalSchema(final EffectiveModelContext globalSchema) {
+ this.globalSchema = globalSchema;
+ dataNormalizer = new DataNormalizer(globalSchema);
+ }
+
+ public DOMYangTextSourceProvider getYangTextSourceProvider() {
+ return yangTextSourceProvider;
+ }
+
+ private void checkPreconditions() {
+ if (globalSchema == null) {
+ throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
+ }
+ }
+
+ @Override
+ @PreDestroy
+ public void close() {
+ listenerRegistration.close();
+ }
+
+ public void setSchemas(final EffectiveModelContext schemas) {
+ onModelContextUpdated(schemas);
+ }
+
+ public InstanceIdentifierContext toInstanceIdentifier(final String restconfInstance) {
+ return toIdentifier(restconfInstance, false);
+ }
+
+ public EffectiveModelContext getGlobalSchema() {
+ return globalSchema;
+ }
+
+ public InstanceIdentifierContext toMountPointIdentifier(final String restconfInstance) {
+ return toIdentifier(restconfInstance, true);
+ }
+
+ private InstanceIdentifierContext toIdentifier(final String restconfInstance,
+ final boolean toMountPointIdentifier) {
+ checkPreconditions();
+
+ if (restconfInstance == null) {
+ return InstanceIdentifierContext.ofLocalRoot(globalSchema);
+ }
+
+ final List<String> pathArgs = urlPathArgsDecode(SLASH_SPLITTER.split(restconfInstance));
+ omitFirstAndLastEmptyString(pathArgs);
+ if (pathArgs.isEmpty()) {
+ return null;
+ }
+
+ final String first = pathArgs.iterator().next();
+ final String startModule = toModuleName(first);
+ if (startModule == null) {
+ throw new RestconfDocumentedException("First node in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final Collection<? extends Module> latestModule = globalSchema.findModules(startModule);
+ if (latestModule.isEmpty()) {
+ throw new RestconfDocumentedException("The module named '" + startModule + "' does not exist.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final InstanceIdentifierContext iiWithSchemaNode = collectPathArguments(YangInstanceIdentifier.builder(),
+ new ArrayDeque<>(), pathArgs, latestModule.iterator().next(), null, toMountPointIdentifier);
+
+ if (iiWithSchemaNode == null) {
+ throw new RestconfDocumentedException("URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ return iiWithSchemaNode;
+ }
+
+ private static List<String> omitFirstAndLastEmptyString(final List<String> list) {
+ if (list.isEmpty()) {
+ return list;
+ }
+
+ final String head = list.iterator().next();
+ if (head.isEmpty()) {
+ list.remove(0);
+ }
+
+ if (list.isEmpty()) {
+ return list;
+ }
+
+ final String last = list.get(list.size() - 1);
+ if (last.isEmpty()) {
+ list.remove(list.size() - 1);
+ }
+
+ return list;
+ }
+
+ public Module findModuleByName(final String moduleName) {
+ checkPreconditions();
+ checkArgument(moduleName != null && !moduleName.isEmpty());
+ return globalSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ }
+
+ public static Module findModuleByName(final DOMMountPoint mountPoint, final String moduleName) {
+ checkArgument(moduleName != null && mountPoint != null);
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mountPoint);
+ return mountPointSchema == null ? null
+ : mountPointSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ }
+
+ public Module findModuleByNamespace(final XMLNamespace namespace) {
+ checkPreconditions();
+ checkArgument(namespace != null);
+ return globalSchema.findModules(namespace).stream().findFirst().orElse(null);
+ }
+
+ public static Module findModuleByNamespace(final DOMMountPoint mountPoint, final XMLNamespace namespace) {
+ checkArgument(namespace != null && mountPoint != null);
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mountPoint);
+ return mountPointSchema == null ? null
+ : mountPointSchema.findModules(namespace).stream().findFirst().orElse(null);
+ }
+
+ public Module findModuleByNameAndRevision(final String name, final Revision revision) {
+ checkPreconditions();
+ checkArgument(name != null && revision != null);
+
+ return globalSchema.findModule(name, revision).orElse(null);
+ }
+
+ public Module findModuleByNameAndRevision(final DOMMountPoint mountPoint, final String name,
+ final Revision revision) {
+ checkPreconditions();
+ checkArgument(name != null && revision != null && mountPoint != null);
+
+ final EffectiveModelContext schemaContext = getModelContext(mountPoint);
+ return schemaContext == null ? null : schemaContext.findModule(name, revision).orElse(null);
+ }
+
+ public DataNodeContainer getDataNodeContainerFor(final YangInstanceIdentifier path) {
+ checkPreconditions();
+
+ final Iterable<PathArgument> elements = path.getPathArguments();
+ final PathArgument head = elements.iterator().next();
+ final QName startQName = head.getNodeType();
+ final Module initialModule = globalSchema.findModule(startQName.getModule()).orElse(null);
+ DataNodeContainer node = initialModule;
+ for (final PathArgument element : elements) {
+ final QName _nodeType = element.getNodeType();
+ final DataSchemaNode potentialNode = childByQName(node, _nodeType);
+ if (potentialNode == null || !isListOrContainer(potentialNode)) {
+ return null;
+ }
+ node = (DataNodeContainer) potentialNode;
+ }
+
+ return node;
+ }
+
+ public String toFullRestconfIdentifier(final YangInstanceIdentifier path, final DOMMountPoint mount) {
+ checkPreconditions();
+
+ final Iterable<PathArgument> elements = path.getPathArguments();
+ final StringBuilder builder = new StringBuilder();
+ final PathArgument head = elements.iterator().next();
+ final QName startQName = head.getNodeType();
+ final EffectiveModelContext schemaContext;
+ if (mount != null) {
+ schemaContext = getModelContext(mount);
+ } else {
+ schemaContext = globalSchema;
+ }
+ final Module initialModule = schemaContext.findModule(startQName.getModule()).orElse(null);
+ DataNodeContainer node = initialModule;
+ for (final PathArgument element : elements) {
+ if (!(element instanceof AugmentationIdentifier)) {
+ final QName _nodeType = element.getNodeType();
+ final DataSchemaNode potentialNode = childByQName(node, _nodeType);
+ if ((!(element instanceof NodeIdentifier) || !(potentialNode instanceof ListSchemaNode))
+ && !(potentialNode instanceof ChoiceSchemaNode)) {
+ builder.append(convertToRestconfIdentifier(element, potentialNode, mount));
+ if (potentialNode instanceof DataNodeContainer) {
+ node = (DataNodeContainer) potentialNode;
+ }
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public String findModuleNameByNamespace(final XMLNamespace namespace) {
+ checkPreconditions();
+
+ final Module module = findModuleByNamespace(namespace);
+ return module == null ? null : module.getName();
+ }
+
+ public static String findModuleNameByNamespace(final DOMMountPoint mountPoint, final XMLNamespace namespace) {
+ final Module module = findModuleByNamespace(mountPoint, namespace);
+ return module == null ? null : module.getName();
+ }
+
+ public XMLNamespace findNamespaceByModuleName(final String moduleName) {
+ final Module module = findModuleByName(moduleName);
+ return module == null ? null : module.getNamespace();
+ }
+
+ public static XMLNamespace findNamespaceByModuleName(final DOMMountPoint mountPoint, final String moduleName) {
+ final Module module = findModuleByName(mountPoint, moduleName);
+ return module == null ? null : module.getNamespace();
+ }
+
+ public Collection<? extends Module> getAllModules(final DOMMountPoint mountPoint) {
+ checkPreconditions();
+
+ final EffectiveModelContext schemaContext = mountPoint == null ? null : getModelContext(mountPoint);
+ return schemaContext == null ? null : schemaContext.getModules();
+ }
+
+ public Collection<? extends Module> getAllModules() {
+ checkPreconditions();
+ return globalSchema.getModules();
+ }
+
+ private static String toRestconfIdentifier(final EffectiveModelContext context, final QName qname) {
+ final Module schema = context.findModule(qname.getModule()).orElse(null);
+ return schema == null ? null : schema.getName() + ':' + qname.getLocalName();
+ }
+
+ public String toRestconfIdentifier(final QName qname, final DOMMountPoint mountPoint) {
+ return mountPoint != null ? toRestconfIdentifier(getModelContext(mountPoint), qname)
+ : toRestconfIdentifier(qname);
+ }
+
+ public String toRestconfIdentifier(final QName qname) {
+ checkPreconditions();
+
+ return toRestconfIdentifier(globalSchema, qname);
+ }
+
+ public static String toRestconfIdentifier(final DOMMountPoint mountPoint, final QName qname) {
+ return mountPoint == null ? null : toRestconfIdentifier(getModelContext(mountPoint), qname);
+ }
+
+ public Module getRestconfModule() {
+ return findModuleByNameAndRevision(Draft02.RestConfModule.NAME, Revision.of(Draft02.RestConfModule.REVISION));
+ }
+
+ public Entry<SchemaInferenceStack, ContainerSchemaNode> getRestconfModuleErrorsSchemaNode() {
+ checkPreconditions();
+
+ final var schema = globalSchema;
+ final var namespace = QNameModule.create(XMLNamespace.of(Draft02.RestConfModule.NAMESPACE),
+ Revision.of(Draft02.RestConfModule.REVISION));
+ if (schema.findModule(namespace).isEmpty()) {
+ return null;
+ }
+
+ final var stack = SchemaInferenceStack.of(globalSchema);
+ stack.enterGrouping(RestConfModule.ERRORS_QNAME);
+ final var stmt = stack.enterSchemaTree(RestConfModule.ERRORS_QNAME);
+ verify(stmt instanceof ContainerSchemaNode, "Unexpected statement %s", stmt);
+ return Map.entry(stack, (ContainerSchemaNode) stmt);
+ }
+
+ public DataSchemaNode getRestconfModuleRestConfSchemaNode(final Module inRestconfModule,
+ final String schemaNodeName) {
+ Module restconfModule = inRestconfModule;
+ if (restconfModule == null) {
+ restconfModule = getRestconfModule();
+ }
+
+ if (restconfModule == null) {
+ return null;
+ }
+
+ final Collection<? extends GroupingDefinition> groupings = restconfModule.getGroupings();
+ final Iterable<? extends GroupingDefinition> filteredGroups = Iterables.filter(groupings,
+ g -> RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE.equals(g.getQName().getLocalName()));
+ final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
+
+ final var instanceDataChildrenByName = findInstanceDataChildrenByName(restconfGrouping,
+ RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode restconfContainer = getFirst(instanceDataChildrenByName);
+
+ if (RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.STREAM_LIST_SCHEMA_NODE.equals(schemaNodeName)) {
+ var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = getFirst(instances);
+ instances = findInstanceDataChildrenByName((DataNodeContainer) modules,
+ RestConfModule.STREAM_LIST_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.MODULES_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.MODULE_LIST_SCHEMA_NODE.equals(schemaNodeName)) {
+ var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = getFirst(instances);
+ instances = findInstanceDataChildrenByName((DataNodeContainer) modules,
+ RestConfModule.MODULE_LIST_SCHEMA_NODE);
+ return getFirst(instances);
+ } else if (RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE.equals(schemaNodeName)) {
+ final var instances = findInstanceDataChildrenByName(
+ (DataNodeContainer) restconfContainer, RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return getFirst(instances);
+ }
+
+ return null;
+ }
+
+ public static @Nullable DataSchemaNode getFirst(final List<FoundChild> children) {
+ return children.isEmpty() ? null : children.get(0).child;
+ }
+
+ private static DataSchemaNode childByQName(final ChoiceSchemaNode container, final QName name) {
+ for (final CaseSchemaNode caze : container.getCases()) {
+ final DataSchemaNode ret = childByQName(caze, name);
+ if (ret != null) {
+ return ret;
+ }
+ }
+
+ return null;
+ }
+
+ private static DataSchemaNode childByQName(final CaseSchemaNode container, final QName name) {
+ return container.dataChildByName(name);
+ }
+
+ private static DataSchemaNode childByQName(final ContainerSchemaNode container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final ListSchemaNode container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final Module container, final QName name) {
+ return dataNodeChildByQName(container, name);
+ }
+
+ private static DataSchemaNode childByQName(final DataSchemaNode container, final QName name) {
+ return null;
+ }
+
+
+ private static DataSchemaNode childByQName(final Object container, final QName name) {
+ if (container instanceof CaseSchemaNode) {
+ return childByQName((CaseSchemaNode) container, name);
+ } else if (container instanceof ChoiceSchemaNode) {
+ return childByQName((ChoiceSchemaNode) container, name);
+ } else if (container instanceof ContainerSchemaNode) {
+ return childByQName((ContainerSchemaNode) container, name);
+ } else if (container instanceof ListSchemaNode) {
+ return childByQName((ListSchemaNode) container, name);
+ } else if (container instanceof DataSchemaNode) {
+ return childByQName((DataSchemaNode) container, name);
+ } else if (container instanceof Module) {
+ return childByQName((Module) container, name);
+ } else {
+ throw new IllegalArgumentException("Unhandled parameter types: "
+ + Arrays.asList(container, name).toString());
+ }
+ }
+
+ private static DataSchemaNode dataNodeChildByQName(final DataNodeContainer container, final QName name) {
+ final DataSchemaNode ret = container.dataChildByName(name);
+ if (ret == null) {
+ for (final DataSchemaNode node : container.getChildNodes()) {
+ if (node instanceof ChoiceSchemaNode) {
+ final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) node;
+ final DataSchemaNode childByQName = childByQName(choiceNode, name);
+ if (childByQName != null) {
+ return childByQName;
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ private String toUriString(final Object object, final LeafSchemaNode leafNode, final DOMMountPoint mount)
+ throws UnsupportedEncodingException {
+ final IllegalArgumentCodec<Object, Object> codec = RestCodec.from(leafNode.getType(), mount, this);
+ return object == null ? "" : URLEncoder.encode(codec.serialize(object).toString(), StandardCharsets.UTF_8);
+ }
+
+ @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Unrecognised NullableDecl")
+ private InstanceIdentifierContext collectPathArguments(final InstanceIdentifierBuilder builder,
+ final Deque<QName> schemaPath, final List<String> strings, final DataNodeContainer parentNode,
+ final DOMMountPoint mountPoint, final boolean returnJustMountPoint) {
+ requireNonNull(strings);
+
+ if (parentNode == null) {
+ return null;
+ }
+
+ if (strings.isEmpty()) {
+ return createContext(builder.build(), (DataSchemaNode) parentNode, mountPoint,
+ mountPoint != null ? getModelContext(mountPoint) : globalSchema);
+ }
+
+ final String head = strings.iterator().next();
+
+ if (head.isEmpty()) {
+ final List<String> remaining = strings.subList(1, strings.size());
+ return collectPathArguments(builder, schemaPath, remaining, parentNode, mountPoint, returnJustMountPoint);
+ }
+
+ final String nodeName = toNodeName(head);
+ final String moduleName = toModuleName(head);
+
+ DataSchemaNode targetNode = null;
+ if (!Strings.isNullOrEmpty(moduleName)) {
+ if (MOUNT_MODULE.equals(moduleName) && MOUNT_NODE.equals(nodeName)) {
+ if (mountPoint != null) {
+ throw new RestconfDocumentedException("Restconf supports just one mount point in URI.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ if (mountService == null) {
+ throw new RestconfDocumentedException(
+ "MountService was not found. Finding behind mount points does not work.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ final YangInstanceIdentifier partialPath = dataNormalizer.toNormalized(builder.build()).getKey();
+ final Optional<DOMMountPoint> mountOpt = mountService.getMountPoint(partialPath);
+ if (mountOpt.isEmpty()) {
+ LOG.debug("Instance identifier to missing mount point: {}", partialPath);
+ throw new RestconfDocumentedException("Mount point does not exist.", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ }
+ final DOMMountPoint mount = mountOpt.get();
+
+ final EffectiveModelContext mountPointSchema = getModelContext(mount);
+ if (mountPointSchema == null) {
+ throw new RestconfDocumentedException("Mount point does not contain any schema with modules.",
+ ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ if (returnJustMountPoint || strings.size() == 1) {
+ return InstanceIdentifierContext.ofMountPointRoot(mount, mountPointSchema);
+ }
+
+ final String moduleNameBehindMountPoint = toModuleName(strings.get(1));
+ if (moduleNameBehindMountPoint == null) {
+ throw new RestconfDocumentedException(
+ "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final Iterator<? extends Module> it = mountPointSchema.findModules(moduleNameBehindMountPoint)
+ .iterator();
+ if (!it.hasNext()) {
+ throw new RestconfDocumentedException("\"" + moduleNameBehindMountPoint
+ + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final List<String> subList = strings.subList(1, strings.size());
+ return collectPathArguments(YangInstanceIdentifier.builder(), new ArrayDeque<>(), subList, it.next(),
+ mount, returnJustMountPoint);
+ }
+
+ Module module = null;
+ if (mountPoint == null) {
+ checkPreconditions();
+ module = globalSchema.findModules(moduleName).stream().findFirst().orElse(null);
+ if (module == null) {
+ throw new RestconfDocumentedException("\"" + moduleName + "\" module does not exist.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+ } else {
+ final EffectiveModelContext schemaContext = getModelContext(mountPoint);
+ if (schemaContext != null) {
+ module = schemaContext.findModules(moduleName).stream().findFirst().orElse(null);
+ } else {
+ module = null;
+ }
+ if (module == null) {
+ throw new RestconfDocumentedException("\"" + moduleName
+ + "\" module does not exist in mount point.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+ }
+
+ final var found = findInstanceDataChildByNameAndNamespace(parentNode, nodeName, module.getNamespace());
+ if (found == null) {
+ if (parentNode instanceof Module) {
+ final RpcDefinition rpc;
+ if (mountPoint == null) {
+ rpc = getRpcDefinition(head, module.getRevision());
+ } else {
+ rpc = getRpcDefinition(module, toNodeName(head));
+ }
+ if (rpc != null) {
+ final var ctx = mountPoint == null ? globalSchema : getModelContext(mountPoint);
+ return InstanceIdentifierContext.ofRpcInput(ctx, rpc, mountPoint);
+ }
+ }
+ throw new RestconfDocumentedException("URI has bad format. Possible reasons:\n" + " 1. \"" + head
+ + "\" was not found in parent data node.\n" + " 2. \"" + head
+ + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + head + "\".",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ targetNode = found.child;
+ schemaPath.addAll(found.intermediate);
+ schemaPath.add(targetNode.getQName());
+ } else {
+ final var potentialSchemaNodes = findInstanceDataChildrenByName(parentNode, nodeName);
+ if (potentialSchemaNodes.size() > 1) {
+ final StringBuilder strBuilder = new StringBuilder();
+ for (var potentialNodeSchema : potentialSchemaNodes) {
+ strBuilder.append(" ").append(potentialNodeSchema.child.getQName().getNamespace()).append("\n");
+ }
+
+ throw new RestconfDocumentedException(
+ "URI has bad format. Node \""
+ + nodeName + "\" is added as augment from more than one module. "
+ + "Therefore the node must have module name "
+ + "and it has to be in format \"moduleName:nodeName\"."
+ + "\nThe node is added as augment from modules with namespaces:\n"
+ + strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ if (potentialSchemaNodes.isEmpty()) {
+ throw new RestconfDocumentedException("\"" + nodeName + "\" in URI was not found in parent data node",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final var found = potentialSchemaNodes.get(0);
+ targetNode = found.child;
+ schemaPath.addAll(found.intermediate);
+ schemaPath.add(targetNode.getQName());
+ }
+
+ if (!isListOrContainer(targetNode)) {
+ throw new RestconfDocumentedException("URI has bad format. Node \"" + head
+ + "\" must be Container or List yang type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ int consumed = 1;
+ if (targetNode instanceof ListSchemaNode) {
+ final ListSchemaNode listNode = (ListSchemaNode) targetNode;
+ final int keysSize = listNode.getKeyDefinition().size();
+ if (strings.size() - consumed < keysSize) {
+ throw new RestconfDocumentedException("Missing key for list \"" + listNode.getQName().getLocalName()
+ + "\".", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+ }
+
+ final List<String> uriKeyValues = strings.subList(consumed, consumed + keysSize);
+ final HashMap<QName, Object> keyValues = new HashMap<>();
+ int index = 0;
+ for (final QName key : listNode.getKeyDefinition()) {
+ final String uriKeyValue = uriKeyValues.get(index);
+ if (uriKeyValue.equals(NULL_VALUE)) {
+ throw new RestconfDocumentedException("URI has bad format. List \""
+ + listNode.getQName().getLocalName() + "\" cannot contain \"null\" value as a key.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final var keyChild = listNode.getDataChildByName(key);
+ schemaPath.addLast(keyChild.getQName());
+ addKeyValue(keyValues, schemaPath, keyChild, uriKeyValue, mountPoint);
+ schemaPath.removeLast();
+ index++;
+ }
+
+ consumed = consumed + index;
+ builder.nodeWithKey(targetNode.getQName(), keyValues);
+ } else {
+ builder.node(targetNode.getQName());
+ }
+
+ if (targetNode instanceof DataNodeContainer) {
+ final List<String> remaining = strings.subList(consumed, strings.size());
+ return collectPathArguments(builder, schemaPath, remaining, (DataNodeContainer) targetNode, mountPoint,
+ returnJustMountPoint);
+ }
+
+ return createContext(builder.build(), targetNode, mountPoint,
+ mountPoint != null ? getModelContext(mountPoint) : globalSchema);
+ }
+
+ private static InstanceIdentifierContext createContext(final YangInstanceIdentifier instance,
+ final DataSchemaNode dataSchemaNode, final DOMMountPoint mountPoint,
+ final EffectiveModelContext schemaContext) {
+ final var normalized = new DataNormalizer(schemaContext).toNormalized(instance);
+ return InstanceIdentifierContext.ofPath(normalized.getValue(), dataSchemaNode, normalized.getKey(), mountPoint);
+ }
+
+ public static @Nullable FoundChild findInstanceDataChildByNameAndNamespace(final DataNodeContainer container,
+ final String name, final XMLNamespace namespace) {
+ requireNonNull(namespace);
+
+ for (var node : findInstanceDataChildrenByName(container, name)) {
+ if (namespace.equals(node.child.getQName().getNamespace())) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ public static List<FoundChild> findInstanceDataChildrenByName(final DataNodeContainer container,
+ final String name) {
+ final List<FoundChild> instantiatedDataNodeContainers = new ArrayList<>();
+ collectInstanceDataNodeContainers(instantiatedDataNodeContainers, requireNonNull(container),
+ requireNonNull(name), List.of());
+ return instantiatedDataNodeContainers;
+ }
+
+ private static void collectInstanceDataNodeContainers(final List<FoundChild> potentialSchemaNodes,
+ final DataNodeContainer container, final String name, final List<QName> intermediate) {
+ // We perform two iterations to retain breadth-first ordering
+ for (var child : container.getChildNodes()) {
+ if (name.equals(child.getQName().getLocalName()) && isInstantiatedDataSchema(child)) {
+ potentialSchemaNodes.add(new FoundChild(child, intermediate));
+ }
+ }
+
+ for (var child : container.getChildNodes()) {
+ if (child instanceof ChoiceSchemaNode) {
+ for (var caze : ((ChoiceSchemaNode) child).getCases()) {
+ collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name,
+ ImmutableList.<QName>builderWithExpectedSize(intermediate.size() + 2)
+ .addAll(intermediate).add(child.getQName()).add(caze.getQName())
+ .build());
+ }
+ }
+ }
+ }
+
+ public static boolean isInstantiatedDataSchema(final DataSchemaNode node) {
+ return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode
+ || node instanceof ContainerSchemaNode || node instanceof ListSchemaNode
+ || node instanceof AnyxmlSchemaNode;
+ }
+
+ private void addKeyValue(final HashMap<QName, Object> map, final Deque<QName> schemaPath, final DataSchemaNode node,
+ final String uriValue, final DOMMountPoint mountPoint) {
+ checkArgument(node instanceof LeafSchemaNode);
+
+ final EffectiveModelContext schemaContext = mountPoint == null ? globalSchema : getModelContext(mountPoint);
+ final String urlDecoded = urlPathArgDecode(requireNonNull(uriValue));
+ TypeDefinition<?> typedef = ((LeafSchemaNode) node).getType();
+ final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
+ if (baseType instanceof LeafrefTypeDefinition) {
+ final var stack = SchemaInferenceStack.of(schemaContext);
+ schemaPath.forEach(stack::enterSchemaTree);
+ typedef = stack.resolveLeafref((LeafrefTypeDefinition) baseType);
+ }
+ final IllegalArgumentCodec<Object, Object> codec = RestCodec.from(typedef, mountPoint, this);
+ Object decoded = codec.deserialize(urlDecoded);
+ String additionalInfo = "";
+ if (decoded == null) {
+ if (typedef instanceof IdentityrefTypeDefinition) {
+ decoded = toQName(schemaContext, urlDecoded);
+ additionalInfo =
+ "For key which is of type identityref it should be in format module_name:identity_name.";
+ }
+ }
+
+ if (decoded == null) {
+ throw new RestconfDocumentedException(uriValue + " from URI can't be resolved. " + additionalInfo,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ map.put(node.getQName(), decoded);
+ }
+
+ private static String toModuleName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return null;
+ }
+
+ // Make sure there is only one occurrence
+ if (str.indexOf(':', idx + 1) != -1) {
+ return null;
+ }
+
+ return str.substring(0, idx);
+ }
+
+ private static String toNodeName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return str;
+ }
+
+ // Make sure there is only one occurrence
+ if (str.indexOf(':', idx + 1) != -1) {
+ return str;
+ }
+
+ return str.substring(idx + 1);
+ }
+
+ private QName toQName(final EffectiveModelContext schemaContext, final String name,
+ final Optional<Revision> revisionDate) {
+ checkPreconditions();
+ final String module = toModuleName(name);
+ final String node = toNodeName(name);
+ final Module m = schemaContext.findModule(module, revisionDate).orElse(null);
+ return m == null ? null : QName.create(m.getQNameModule(), node);
+ }
+
+ private QName toQName(final EffectiveModelContext schemaContext, final String name) {
+ checkPreconditions();
+ final String module = toModuleName(name);
+ final String node = toNodeName(name);
+ final Collection<? extends Module> modules = schemaContext.findModules(module);
+ return modules.isEmpty() ? null : QName.create(modules.iterator().next().getQNameModule(), node);
+ }
+
+ private static boolean isListOrContainer(final DataSchemaNode node) {
+ return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode;
+ }
+
+ public RpcDefinition getRpcDefinition(final String name, final Optional<Revision> revisionDate) {
+ final QName validName = toQName(globalSchema, name, revisionDate);
+ return validName == null ? null : qnameToRpc.get().get(validName);
+ }
+
+ public RpcDefinition getRpcDefinition(final String name) {
+ final QName validName = toQName(globalSchema, name);
+ return validName == null ? null : qnameToRpc.get().get(validName);
+ }
+
+ private static RpcDefinition getRpcDefinition(final Module module, final String rpcName) {
+ final QName rpcQName = QName.create(module.getQNameModule(), rpcName);
+ for (final RpcDefinition rpcDefinition : module.getRpcs()) {
+ if (rpcQName.equals(rpcDefinition.getQName())) {
+ return rpcDefinition;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onModelContextUpdated(final EffectiveModelContext context) {
+ if (context != null) {
+ final Collection<? extends RpcDefinition> defs = context.getOperations();
+ final Map<QName, RpcDefinition> newMap = new HashMap<>(defs.size());
+
+ for (final RpcDefinition operation : defs) {
+ newMap.put(operation.getQName(), operation);
+ }
+
+ // FIXME: still not completely atomic
+ qnameToRpc.set(ImmutableMap.copyOf(newMap));
+ setGlobalSchema(context);
+ }
+ }
+
+ private static List<String> urlPathArgsDecode(final Iterable<String> strings) {
+ final List<String> decodedPathArgs = new ArrayList<>();
+ for (final String pathArg : strings) {
+ final String _decode = URLDecoder.decode(pathArg, StandardCharsets.UTF_8);
+ decodedPathArgs.add(_decode);
+ }
+ return decodedPathArgs;
+ }
+
+ static String urlPathArgDecode(final String pathArg) {
+ if (pathArg == null) {
+ return null;
+ }
+ return URLDecoder.decode(pathArg, StandardCharsets.UTF_8);
+ }
+
+ private String convertToRestconfIdentifier(final PathArgument argument, final DataSchemaNode node,
+ final DOMMountPoint mount) {
+ if (argument instanceof NodeIdentifier) {
+ return convertToRestconfIdentifier((NodeIdentifier) argument, mount);
+ } else if (argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode) {
+ return convertToRestconfIdentifierWithPredicates((NodeIdentifierWithPredicates) argument,
+ (ListSchemaNode) node, mount);
+ } else if (argument != null && node != null) {
+ throw new IllegalArgumentException("Conversion of generic path argument is not supported");
+ } else {
+ throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.asList(argument, node));
+ }
+ }
+
+ private String convertToRestconfIdentifier(final NodeIdentifier argument, final DOMMountPoint node) {
+ return "/" + toRestconfIdentifier(argument.getNodeType(), node);
+ }
+
+ private String convertToRestconfIdentifierWithPredicates(final NodeIdentifierWithPredicates argument,
+ final ListSchemaNode node, final DOMMountPoint mount) {
+ final QName nodeType = argument.getNodeType();
+ final String nodeIdentifier = toRestconfIdentifier(nodeType, mount);
+
+ final StringBuilder builder = new StringBuilder().append('/').append(nodeIdentifier).append('/');
+
+ final List<QName> keyDefinition = node.getKeyDefinition();
+ boolean hasElements = false;
+ for (final QName key : keyDefinition) {
+ for (final DataSchemaNode listChild : node.getChildNodes()) {
+ if (listChild.getQName().equals(key)) {
+ if (!hasElements) {
+ hasElements = true;
+ } else {
+ builder.append('/');
+ }
+
+ checkState(listChild instanceof LeafSchemaNode,
+ "List key has to consist of leaves, not %s", listChild);
+
+ final Object value = argument.getValue(key);
+ try {
+ builder.append(toUriString(value, (LeafSchemaNode)listChild, mount));
+ } catch (final UnsupportedEncodingException e) {
+ LOG.error("Error parsing URI: {}", value, e);
+ return null;
+ }
+ break;
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public YangInstanceIdentifier toXpathRepresentation(final YangInstanceIdentifier instanceIdentifier) {
+ if (dataNormalizer == null) {
+ throw new RestconfDocumentedException("Data normalizer isn't set. Normalization isn't possible");
+ }
+
+ try {
+ return dataNormalizer.toLegacy(instanceIdentifier);
+ } catch (final DataNormalizationException e) {
+ throw new RestconfDocumentedException("Data normalizer failed. Normalization isn't possible", e);
+ }
+ }
+
+ public boolean isNodeMixin(final YangInstanceIdentifier path) {
+ final DataNormalizationOperation<?> operation;
+ try {
+ operation = dataNormalizer.getOperation(path);
+ } catch (final DataNormalizationException e) {
+ throw new RestconfDocumentedException("Data normalizer failed. Normalization isn't possible", e);
+ }
+ return operation.isMixin();
+ }
+
+ private static EffectiveModelContext getModelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+
+ public static final class FoundChild {
+ // Intermediate schema tree children, usually empty
+ public final @NonNull List<QName> intermediate;
+ public final @NonNull DataSchemaNode child;
+
+ private FoundChild(final DataSchemaNode child, final List<QName> intermediate) {
+ this.child = requireNonNull(child);
+ this.intermediate = requireNonNull(intermediate);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java
new file mode 100644
index 0000000..b9e7214
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationException.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+final class DataNormalizationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ DataNormalizationException(final String message) {
+ super(message);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java
new file mode 100644
index 0000000..eb41c78
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizationOperation.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+abstract class DataNormalizationOperation<T extends PathArgument> implements Identifiable<T> {
+ private final T identifier;
+
+ DataNormalizationOperation(final T identifier) {
+ this.identifier = identifier;
+ }
+
+ static DataNormalizationOperation<?> from(final EffectiveModelContext ctx) {
+ return new ContainerNormalization(ctx);
+ }
+
+ @Override
+ public T getIdentifier() {
+ return identifier;
+ }
+
+ boolean isMixin() {
+ return false;
+ }
+
+ Set<QName> getQNameIdentifiers() {
+ return ImmutableSet.of(identifier.getNodeType());
+ }
+
+ abstract DataNormalizationOperation<?> getChild(PathArgument child) throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> getChild(QName child) throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> enterChild(QName child, SchemaInferenceStack stack)
+ throws DataNormalizationException;
+
+ abstract DataNormalizationOperation<?> enterChild(PathArgument child, SchemaInferenceStack stack)
+ throws DataNormalizationException;
+
+ void pushToStack(final SchemaInferenceStack stack) {
+ // Accurate for most subclasses
+ stack.enterSchemaTree(getIdentifier().getNodeType());
+ }
+
+ private abstract static class SimpleTypeNormalization<T extends PathArgument>
+ extends DataNormalizationOperation<T> {
+ SimpleTypeNormalization(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> getChild(final PathArgument child) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> getChild(final QName child) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+ return null;
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+ return null;
+ }
+ }
+
+ private static final class LeafNormalization extends SimpleTypeNormalization<NodeIdentifier> {
+ LeafNormalization(final LeafSchemaNode potential) {
+ super(new NodeIdentifier(potential.getQName()));
+ }
+ }
+
+ private static final class LeafListEntryNormalization extends SimpleTypeNormalization<NodeWithValue> {
+ LeafListEntryNormalization(final LeafListSchemaNode potential) {
+ super(new NodeWithValue<>(potential.getQName(), Empty.value()));
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private abstract static class DataContainerNormalizationOperation<T extends PathArgument>
+ extends DataNormalizationOperation<T> {
+ private final DataNodeContainer schema;
+ private final Map<QName, DataNormalizationOperation<?>> byQName = new ConcurrentHashMap<>();
+ private final Map<PathArgument, DataNormalizationOperation<?>> byArg = new ConcurrentHashMap<>();
+
+ DataContainerNormalizationOperation(final T identifier, final DataNodeContainer schema) {
+ super(identifier);
+ this.schema = schema;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) throws DataNormalizationException {
+ DataNormalizationOperation<?> potential = byArg.get(child);
+ if (potential != null) {
+ return potential;
+ }
+ potential = fromLocalSchema(child);
+ return register(potential);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) throws DataNormalizationException {
+ DataNormalizationOperation<?> potential = byQName.get(child);
+ if (potential != null) {
+ return potential;
+ }
+ potential = fromLocalSchemaAndQName(schema, child);
+ return register(potential);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ final DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ return pushToStack(getChild(child), stack);
+ }
+
+ private static DataNormalizationOperation<?> pushToStack(final DataNormalizationOperation<?> child,
+ final SchemaInferenceStack stack) {
+ if (child != null) {
+ child.pushToStack(stack);
+ }
+ return child;
+ }
+
+ private DataNormalizationOperation<?> fromLocalSchema(final PathArgument child)
+ throws DataNormalizationException {
+ if (child instanceof AugmentationIdentifier) {
+ return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
+ .iterator().next());
+ }
+ return fromSchemaAndQNameChecked(schema, child.getNodeType());
+ }
+
+ DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema2,
+ final QName child) throws DataNormalizationException {
+ return fromSchemaAndQNameChecked(schema2, child);
+ }
+
+ private DataNormalizationOperation<?> register(final DataNormalizationOperation<?> potential) {
+ if (potential != null) {
+ byArg.put(potential.getIdentifier(), potential);
+ for (final QName qname : potential.getQNameIdentifiers()) {
+ byQName.put(qname, potential);
+ }
+ }
+ return potential;
+ }
+
+ private static DataNormalizationOperation<?> fromSchemaAndQNameChecked(final DataNodeContainer schema,
+ final QName child) throws DataNormalizationException {
+
+ final DataSchemaNode result = findChildSchemaNode(schema, child);
+ if (result == null) {
+ throw new DataNormalizationException(String.format(
+ "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child,
+ schema,schema.getChildNodes()));
+ }
+
+ // We try to look up if this node was added by augmentation
+ if (schema instanceof DataSchemaNode && result.isAugmenting()) {
+ return fromAugmentation(schema, (AugmentationTarget) schema, result);
+ }
+ return fromDataSchemaNode(result);
+ }
+ }
+
+ private static final class ListItemNormalization extends
+ DataContainerNormalizationOperation<NodeIdentifierWithPredicates> {
+ ListItemNormalization(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
+ super(identifier, schema);
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class UnkeyedListItemNormalization
+ extends DataContainerNormalizationOperation<NodeIdentifier> {
+ UnkeyedListItemNormalization(final ListSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()), schema);
+ }
+
+ @Override
+ protected void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class ContainerNormalization extends DataContainerNormalizationOperation<NodeIdentifier> {
+ ContainerNormalization(final ContainerLike schema) {
+ super(new NodeIdentifier(schema.getQName()), schema);
+ }
+ }
+
+ private abstract static class MixinNormalizationOp<T extends PathArgument> extends DataNormalizationOperation<T> {
+ MixinNormalizationOp(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ final boolean isMixin() {
+ return true;
+ }
+ }
+
+ private abstract static class ListLikeNormalizationOp<T extends PathArgument> extends MixinNormalizationOp<T> {
+ ListLikeNormalizationOp(final T identifier) {
+ super(identifier);
+ }
+
+ @Override
+ protected final DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack)
+ throws DataNormalizationException {
+ // Stack is already pointing to the corresponding statement, now we are just working with the child
+ return getChild(child);
+ }
+
+ @Override
+ protected final DataNormalizationOperation<?> enterChild(final PathArgument child,
+ final SchemaInferenceStack stack) throws DataNormalizationException {
+ return getChild(child);
+ }
+ }
+
+ private static final class LeafListMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final DataNormalizationOperation<?> innerOp;
+
+ LeafListMixinNormalization(final LeafListSchemaNode potential) {
+ super(new NodeIdentifier(potential.getQName()));
+ innerOp = new LeafListEntryNormalization(potential);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child instanceof NodeWithValue) {
+ return innerOp;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerOp;
+ }
+ return null;
+ }
+ }
+
+ private static final class AugmentationNormalization
+ extends DataContainerNormalizationOperation<AugmentationIdentifier> {
+
+ AugmentationNormalization(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
+ super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
+ new EffectiveAugmentationSchema(augmentation, schema));
+ }
+
+ @Override
+ boolean isMixin() {
+ return true;
+ }
+
+ @Override
+ DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema, final QName child) {
+ final DataSchemaNode result = findChildSchemaNode(schema, child);
+ if (result == null) {
+ return null;
+ }
+
+ // We try to look up if this node was added by augmentation
+ if (schema instanceof DataSchemaNode && result.isAugmenting()) {
+ return fromAugmentation(schema, (AugmentationTarget) schema, result);
+ }
+ return fromDataSchemaNode(result);
+ }
+
+ @Override
+ Set<QName> getQNameIdentifiers() {
+ return getIdentifier().getPossibleChildNames();
+ }
+
+ @Override
+ void pushToStack(final SchemaInferenceStack stack) {
+ // No-op
+ }
+ }
+
+ private static final class MapMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final ListItemNormalization innerNode;
+
+ MapMixinNormalization(final ListSchemaNode list) {
+ super(new NodeIdentifier(list.getQName()));
+ innerNode = new ListItemNormalization(NodeIdentifierWithPredicates.of(list.getQName()), list);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child.getNodeType().equals(getIdentifier().getNodeType())) {
+ return innerNode;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerNode;
+ }
+ return null;
+ }
+ }
+
+ private static final class UnkeyedListMixinNormalization extends ListLikeNormalizationOp<NodeIdentifier> {
+ private final UnkeyedListItemNormalization innerNode;
+
+ UnkeyedListMixinNormalization(final ListSchemaNode list) {
+ super(new NodeIdentifier(list.getQName()));
+ innerNode = new UnkeyedListItemNormalization(list);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ if (child.getNodeType().equals(getIdentifier().getNodeType())) {
+ return innerNode;
+ }
+ return null;
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ if (getIdentifier().getNodeType().equals(child)) {
+ return innerNode;
+ }
+ return null;
+ }
+ }
+
+ private static final class ChoiceNodeNormalization extends MixinNormalizationOp<NodeIdentifier> {
+ private final ImmutableMap<QName, DataNormalizationOperation<?>> byQName;
+ private final ImmutableMap<PathArgument, DataNormalizationOperation<?>> byArg;
+ private final ImmutableMap<DataNormalizationOperation<?>, QName> childToCase;
+
+ ChoiceNodeNormalization(final ChoiceSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()));
+ ImmutableMap.Builder<DataNormalizationOperation<?>, QName> childToCaseBuilder = ImmutableMap.builder();
+ final ImmutableMap.Builder<QName, DataNormalizationOperation<?>> byQNameBuilder = ImmutableMap.builder();
+ final ImmutableMap.Builder<PathArgument, DataNormalizationOperation<?>> byArgBuilder =
+ ImmutableMap.builder();
+
+ for (final CaseSchemaNode caze : schema.getCases()) {
+ for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
+ final DataNormalizationOperation<?> childOp = fromDataSchemaNode(cazeChild);
+ byArgBuilder.put(childOp.getIdentifier(), childOp);
+ childToCaseBuilder.put(childOp, caze.getQName());
+ for (final QName qname : childOp.getQNameIdentifiers()) {
+ byQNameBuilder.put(qname, childOp);
+ }
+ }
+ }
+ childToCase = childToCaseBuilder.build();
+ byQName = byQNameBuilder.build();
+ byArg = byArgBuilder.build();
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final PathArgument child) {
+ return byArg.get(child);
+ }
+
+ @Override
+ DataNormalizationOperation<?> getChild(final QName child) {
+ return byQName.get(child);
+ }
+
+ @Override
+ Set<QName> getQNameIdentifiers() {
+ return byQName.keySet();
+ }
+
+ @Override
+ DataNormalizationOperation<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ DataNormalizationOperation<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+ return pushToStack(getChild(child), stack);
+ }
+
+ @Override
+ void pushToStack(final SchemaInferenceStack stack) {
+ stack.enterChoice(getIdentifier().getNodeType());
+ }
+
+ private DataNormalizationOperation<?> pushToStack(final DataNormalizationOperation<?> child,
+ final SchemaInferenceStack stack) {
+ if (child != null) {
+ final var caseName = verifyNotNull(childToCase.get(child), "No case statement for %s in %s", child,
+ this);
+ stack.enterSchemaTree(caseName);
+ child.pushToStack(stack);
+ }
+ return child;
+ }
+ }
+
+ private static final class AnyxmlNormalization extends SimpleTypeNormalization<NodeIdentifier> {
+ AnyxmlNormalization(final AnyxmlSchemaNode schema) {
+ super(new NodeIdentifier(schema.getQName()));
+ }
+ }
+
+ private static @Nullable DataSchemaNode findChildSchemaNode(final DataNodeContainer parent, final QName child) {
+ final DataSchemaNode potential = parent.dataChildByName(child);
+ return potential != null ? potential : findChoice(parent, child);
+ }
+
+ private static @Nullable ChoiceSchemaNode findChoice(final DataNodeContainer parent, final QName child) {
+ for (final ChoiceSchemaNode choice : Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class)) {
+ for (final CaseSchemaNode caze : choice.getCases()) {
+ if (findChildSchemaNode(caze, child) != null) {
+ return choice;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a DataNormalizationOperation for provided child node.
+ *
+ * <p>
+ * If supplied child is added by Augmentation this operation returns
+ * a DataNormalizationOperation for augmentation,
+ * otherwise returns a DataNormalizationOperation for child as
+ * call for {@link #fromDataSchemaNode(DataSchemaNode)}.
+ */
+ private static DataNormalizationOperation<?> fromAugmentation(final DataNodeContainer parent,
+ final AugmentationTarget parentAug, final DataSchemaNode child) {
+ for (final AugmentationSchemaNode aug : parentAug.getAvailableAugmentations()) {
+ if (aug.dataChildByName(child.getQName()) != null) {
+ return new AugmentationNormalization(aug, parent);
+ }
+ }
+ return fromDataSchemaNode(child);
+ }
+
+ static DataNormalizationOperation<?> fromDataSchemaNode(final DataSchemaNode potential) {
+ if (potential instanceof ContainerSchemaNode) {
+ return new ContainerNormalization((ContainerSchemaNode) potential);
+ } else if (potential instanceof ListSchemaNode) {
+ return fromListSchemaNode((ListSchemaNode) potential);
+ } else if (potential instanceof LeafSchemaNode) {
+ return new LeafNormalization((LeafSchemaNode) potential);
+ } else if (potential instanceof ChoiceSchemaNode) {
+ return new ChoiceNodeNormalization((ChoiceSchemaNode) potential);
+ } else if (potential instanceof LeafListSchemaNode) {
+ return new LeafListMixinNormalization((LeafListSchemaNode) potential);
+ } else if (potential instanceof AnyxmlSchemaNode) {
+ return new AnyxmlNormalization((AnyxmlSchemaNode) potential);
+ }
+ return null;
+ }
+
+ private static DataNormalizationOperation<?> fromListSchemaNode(final ListSchemaNode potential) {
+ if (potential.getKeyDefinition().isEmpty()) {
+ return new UnkeyedListMixinNormalization(potential);
+ }
+ return new MapMixinNormalization(potential);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java
new file mode 100644
index 0000000..334a6c7
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/DataNormalizer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+class DataNormalizer {
+ private final DataNormalizationOperation<?> operation;
+ private final EffectiveModelContext context;
+
+ DataNormalizer(final EffectiveModelContext ctx) {
+ context = requireNonNull(ctx);
+ operation = DataNormalizationOperation.from(ctx);
+ }
+
+ Entry<YangInstanceIdentifier, SchemaInferenceStack> toNormalized(final YangInstanceIdentifier legacy) {
+ List<PathArgument> normalizedArgs = new ArrayList<>();
+
+ DataNormalizationOperation<?> currentOp = operation;
+ Iterator<PathArgument> arguments = legacy.getPathArguments().iterator();
+ SchemaInferenceStack stack = SchemaInferenceStack.of(context);
+
+ try {
+ while (arguments.hasNext()) {
+ PathArgument legacyArg = arguments.next();
+ currentOp = currentOp.enterChild(legacyArg, stack);
+ checkArgument(currentOp != null,
+ "Legacy Instance Identifier %s is not correct. Normalized Instance Identifier so far %s",
+ legacy, normalizedArgs);
+ while (currentOp.isMixin()) {
+ normalizedArgs.add(currentOp.getIdentifier());
+ currentOp = currentOp.enterChild(legacyArg.getNodeType(), stack);
+ }
+ normalizedArgs.add(legacyArg);
+ }
+ } catch (DataNormalizationException e) {
+ throw new IllegalArgumentException("Failed to normalize path " + legacy, e);
+ }
+
+ return Map.entry(YangInstanceIdentifier.create(normalizedArgs), stack);
+ }
+
+ DataNormalizationOperation<?> getOperation(final YangInstanceIdentifier legacy)
+ throws DataNormalizationException {
+ DataNormalizationOperation<?> currentOp = operation;
+
+ for (PathArgument pathArgument : legacy.getPathArguments()) {
+ currentOp = currentOp.getChild(pathArgument);
+ }
+ return currentOp;
+ }
+
+ YangInstanceIdentifier toLegacy(final YangInstanceIdentifier normalized) throws DataNormalizationException {
+ ImmutableList.Builder<PathArgument> legacyArgs = ImmutableList.builder();
+ DataNormalizationOperation<?> currentOp = operation;
+ for (PathArgument normalizedArg : normalized.getPathArguments()) {
+ currentOp = currentOp.getChild(normalizedArg);
+ if (!currentOp.isMixin()) {
+ legacyArgs.add(normalizedArg);
+ }
+ }
+ return YangInstanceIdentifier.create(legacyArgs.build());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java
new file mode 100644
index 0000000..296fd91
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Optional;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
+import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
+import org.opendaylight.netconf.sal.rest.impl.PatchJsonBodyWriter;
+import org.opendaylight.netconf.sal.restconf.api.JSONRestconfService;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.util.SimpleUriInfo;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the JSONRestconfService interface using the restconf Draft02 implementation.
+ *
+ * @author Thomas Pantelis
+ * @deprecated Replaced by {JSONRestconfServiceRfc8040Impl from restconf-nb-rfc8040
+ */
+@Singleton
+@Deprecated
+public class JSONRestconfServiceImpl implements JSONRestconfService {
+ private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceImpl.class);
+
+ private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
+
+ private final ControllerContext controllerContext;
+ private final RestconfService restconfService;
+
+ @Inject
+ public JSONRestconfServiceImpl(final ControllerContext controllerContext, final RestconfImpl restconfService) {
+ this.controllerContext = controllerContext;
+ this.restconfService = restconfService;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void put(final String uriPath, final String payload) throws OperationFailedException {
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, false,
+ controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ restconfService.updateConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "PUT");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void post(final String uriPath, final String payload) throws OperationFailedException {
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true,
+ controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ restconfService.createConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "POST");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public void delete(final String uriPath) throws OperationFailedException {
+ LOG.debug("delete: uriPath: {}", uriPath);
+
+ try {
+ restconfService.deleteConfigurationData(uriPath);
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "DELETE");
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
+ throws OperationFailedException {
+ LOG.debug("get: uriPath: {}", uriPath);
+
+ try {
+ NormalizedNodeContext readData;
+ final SimpleUriInfo uriInfo = new SimpleUriInfo(uriPath);
+ if (datastoreType == LogicalDatastoreType.CONFIGURATION) {
+ readData = restconfService.readConfigurationData(uriPath, uriInfo);
+ } else {
+ readData = restconfService.readOperationalData(uriPath, uriInfo);
+ }
+
+ final Optional<String> result = Optional.of(toJson(readData));
+
+ LOG.debug("get returning: {}", result.get());
+
+ return result;
+ } catch (final Exception e) {
+ if (!isDataMissing(e)) {
+ propagateExceptionAs(uriPath, e, "GET");
+ }
+
+ LOG.debug("Data missing - returning absent");
+ return Optional.empty();
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @SuppressFBWarnings(value = "NP_NULL_PARAM_DEREF", justification = "Unrecognised NullableDecl")
+ @Override
+ public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
+ throws OperationFailedException {
+ requireNonNull(uriPath, "uriPath can't be null");
+
+ final String actualInput = input.isPresent() ? input.get() : null;
+
+ LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
+
+ String output = null;
+ try {
+ NormalizedNodeContext outputContext;
+ if (actualInput != null) {
+ final InputStream entityStream = new ByteArrayInputStream(actualInput.getBytes(StandardCharsets.UTF_8));
+ final NormalizedNodeContext inputContext =
+ JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true, controllerContext);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
+ .getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
+
+ outputContext = restconfService.invokeRpc(uriPath, inputContext, null);
+ } else {
+ outputContext = restconfService.invokeRpc(uriPath, null, null);
+ }
+
+ if (outputContext.getData() != null) {
+ output = toJson(outputContext);
+ }
+ } catch (final RuntimeException | IOException e) {
+ propagateExceptionAs(uriPath, e, "RPC");
+ }
+
+ return Optional.ofNullable(output);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> patch(final String uriPath, final String payload)
+ throws OperationFailedException {
+
+ String output = null;
+ requireNonNull(payload, "payload can't be null");
+
+ LOG.debug("patch: uriPath: {}, payload: {}", uriPath, payload);
+
+ final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
+
+ JsonToPatchBodyReader jsonToPatchBodyReader = new JsonToPatchBodyReader(controllerContext);
+ final PatchContext context = jsonToPatchBodyReader.readFrom(uriPath, entityStream);
+
+ LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
+ LOG.debug("Parsed NormalizedNode: {}", context.getData());
+
+ try {
+ PatchStatusContext patchStatusContext = restconfService
+ .patchConfigurationData(context, new SimpleUriInfo(uriPath));
+ output = toJson(patchStatusContext);
+ } catch (final Exception e) {
+ propagateExceptionAs(uriPath, e, "PATCH");
+ }
+ return Optional.ofNullable(output);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Optional<String> subscribeToStream(final String identifier, final MultivaluedMap<String, String> params)
+ throws OperationFailedException {
+ //Note: We use http://127.0.0.1 because the Uri parser requires something there though it does nothing
+ String uri = new StringBuilder("http://127.0.0.1:8081/restconf/streams/stream/").append(identifier).toString();
+ MultivaluedMap queryParams = params != null ? params : new MultivaluedHashMap<String, String>();
+ UriInfo uriInfo = new SimpleUriInfo(uri, queryParams);
+
+ String jsonRes = null;
+ try {
+ NormalizedNodeContext res = restconfService.subscribeToStream(identifier, uriInfo);
+ jsonRes = toJson(res);
+ } catch (final Exception e) {
+ propagateExceptionAs(identifier, e, "RPC");
+ }
+
+ return Optional.ofNullable(jsonRes);
+ }
+
+ private static String toJson(final PatchStatusContext patchStatusContext) throws IOException {
+ final PatchJsonBodyWriter writer = new PatchJsonBodyWriter();
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writer.writeTo(patchStatusContext, PatchStatusContext.class, null, EMPTY_ANNOTATIONS,
+ MediaType.APPLICATION_JSON_TYPE, null, outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static String toJson(final NormalizedNodeContext readData) throws IOException {
+ final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
+ MediaType.APPLICATION_JSON_TYPE, null, outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
+ private static boolean isDataMissing(final Exception exception) {
+ boolean dataMissing = false;
+ if (exception instanceof RestconfDocumentedException) {
+ final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
+ if (!rde.getErrors().isEmpty()) {
+ if (rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING) {
+ dataMissing = true;
+ }
+ }
+ }
+
+ return dataMissing;
+ }
+
+ private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
+ throws OperationFailedException {
+ LOG.debug("Error for uriPath: {}", uriPath, exception);
+
+ if (exception instanceof RestconfDocumentedException) {
+ throw new OperationFailedException(String.format(
+ "%s failed for URI %s", operation, uriPath), exception.getCause(),
+ toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
+ }
+
+ throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
+ }
+
+ private static RpcError[] toRpcErrors(final List<RestconfError> from) {
+ final RpcError[] to = new RpcError[from.size()];
+ int index = 0;
+ for (final RestconfError e: from) {
+ to[index++] = RpcResultBuilder.newError(e.getErrorType(), e.getErrorTag(), e.getErrorMessage());
+ }
+
+ return to;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java
new file mode 100644
index 0000000..975da60
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/NormalizedDataPrunner.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+
+class NormalizedDataPrunner {
+
+ public DataContainerChild pruneDataAtDepth(final DataContainerChild node, final Integer depth) {
+ if (depth == null) {
+ return node;
+ }
+
+ if (node instanceof LeafNode || node instanceof LeafSetNode || node instanceof AnyxmlNode) {
+ return node;
+ } else if (node instanceof MixinNode) {
+ return processMixinNode(node, depth);
+ } else if (node instanceof DataContainerNode) {
+ return processContainerNode(node, depth);
+ }
+ throw new IllegalStateException("Unexpected Mixin node occured why pruning data to requested depth");
+ }
+
+ private DataContainerChild processMixinNode(final NormalizedNode node, final Integer depth) {
+ if (node instanceof AugmentationNode) {
+ return processAugmentationNode(node, depth);
+ } else if (node instanceof ChoiceNode) {
+ return processChoiceNode(node, depth);
+ } else if (node instanceof UserMapNode) {
+ return processOrderedMapNode(node, depth);
+ } else if (node instanceof MapNode) {
+ return processMapNode(node, depth);
+ } else if (node instanceof UnkeyedListNode) {
+ return processUnkeyedListNode(node, depth);
+ }
+ throw new IllegalStateException("Unexpected Mixin node occured why pruning data to requested depth");
+ }
+
+ private DataContainerChild processContainerNode(final NormalizedNode node, final Integer depth) {
+ final ContainerNode containerNode = (ContainerNode) node;
+ DataContainerNodeBuilder<NodeIdentifier, ContainerNode> newContainerBuilder = Builders.containerBuilder()
+ .withNodeIdentifier(containerNode.getIdentifier());
+ if (depth > 1) {
+ processDataContainerChild((DataContainerNode) node, depth, newContainerBuilder);
+ }
+ return newContainerBuilder.build();
+ }
+
+ private DataContainerChild processChoiceNode(final NormalizedNode node, final Integer depth) {
+ final ChoiceNode choiceNode = (ChoiceNode) node;
+ DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> newChoiceBuilder = Builders.choiceBuilder()
+ .withNodeIdentifier(choiceNode.getIdentifier());
+
+ processDataContainerChild((DataContainerNode) node, depth, newChoiceBuilder);
+
+ return newChoiceBuilder.build();
+ }
+
+ private DataContainerChild processAugmentationNode(final NormalizedNode node, final Integer depth) {
+ final AugmentationNode augmentationNode = (AugmentationNode) node;
+ DataContainerNodeBuilder<AugmentationIdentifier, ? extends DataContainerChild> newAugmentationBuilder =
+ Builders.augmentationBuilder().withNodeIdentifier(augmentationNode.getIdentifier());
+
+ processDataContainerChild((DataContainerNode) node, depth, newAugmentationBuilder);
+
+ return newAugmentationBuilder.build();
+ }
+
+ private void processDataContainerChild(final DataContainerNode node, final Integer depth,
+ final DataContainerNodeBuilder<?, ?> newBuilder) {
+ for (DataContainerChild nodeValue : node.body()) {
+ newBuilder.withChild(pruneDataAtDepth(nodeValue, depth - 1));
+ }
+ }
+
+ private DataContainerChild processUnkeyedListNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> newUnkeyedListBuilder = Builders
+ .unkeyedListBuilder();
+ if (depth > 1) {
+ for (UnkeyedListEntryNode oldUnkeyedListEntry : ((UnkeyedListNode) node).body()) {
+ DataContainerNodeBuilder<NodeIdentifier, UnkeyedListEntryNode> newUnkeyedListEntry = Builders
+ .unkeyedListEntryBuilder().withNodeIdentifier(oldUnkeyedListEntry.getIdentifier());
+ for (DataContainerChild oldUnkeyedListEntryValue : oldUnkeyedListEntry.body()) {
+ newUnkeyedListEntry.withChild(pruneDataAtDepth(oldUnkeyedListEntryValue, depth - 1));
+ }
+ newUnkeyedListBuilder.addChild(newUnkeyedListEntry.build());
+ }
+ }
+ return newUnkeyedListBuilder.build();
+ }
+
+ private DataContainerChild processOrderedMapNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<MapEntryNode, UserMapNode> newOrderedMapNodeBuilder = Builders.orderedMapBuilder();
+ processMapEntries(node, depth, newOrderedMapNodeBuilder);
+ return newOrderedMapNodeBuilder.build();
+ }
+
+ private DataContainerChild processMapNode(final NormalizedNode node, final Integer depth) {
+ CollectionNodeBuilder<MapEntryNode, SystemMapNode> newMapNodeBuilder = Builders.mapBuilder();
+ processMapEntries(node, depth, newMapNodeBuilder);
+ return newMapNodeBuilder.build();
+ }
+
+ private void processMapEntries(final NormalizedNode node, final Integer depth,
+ final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> newOrderedMapNodeBuilder) {
+ if (depth > 1) {
+ for (MapEntryNode oldMapEntryNode : ((MapNode) node).body()) {
+ DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> newMapEntryNodeBuilder =
+ Builders.mapEntryBuilder().withNodeIdentifier(oldMapEntryNode.getIdentifier());
+ for (DataContainerChild mapEntryNodeValue : oldMapEntryNode.body()) {
+ newMapEntryNodeBuilder.withChild(pruneDataAtDepth(mapEntryNodeValue, depth - 1));
+ }
+ newOrderedMapNodeBuilder.withChild(newMapEntryNodeBuilder.build());
+ }
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java
new file mode 100644
index 0000000..87e33b9
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.util.concurrent.FluentFuture;
+import javax.ws.rs.core.Response.Status;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+
+/**
+ * Wrapper for status and future of PUT operation.
+ */
+public class PutResult {
+ private final Status status;
+ private final FluentFuture<? extends CommitInfo> future;
+
+ /**
+ * Wrap status and future by constructor - make this immutable.
+ *
+ * @param status
+ * status of operations
+ * @param future
+ * result of submit of PUT operation
+ */
+ public PutResult(final Status status, final FluentFuture<? extends CommitInfo> future) {
+ this.status = status;
+ this.future = future;
+ }
+
+ /**
+ * Get status.
+ *
+ * @return {@link Status} result
+ */
+ public Status getStatus() {
+ return this.status;
+ }
+
+ /**
+ * Get future.
+ *
+ * @return {@link FluentFuture} result
+ */
+ public FluentFuture<? extends CommitInfo> getFutureOfPutData() {
+ return this.future;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java
new file mode 100644
index 0000000..098c53b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import com.google.common.base.Strings;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.impl.WriterParameters;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+
+public final class QueryParametersParser {
+
+ private enum UriParameters {
+ PRETTY_PRINT("prettyPrint"),
+ DEPTH("depth");
+
+ private final String uriParameterName;
+
+ UriParameters(final String uriParameterName) {
+ this.uriParameterName = uriParameterName;
+ }
+
+ @Override
+ public String toString() {
+ return uriParameterName;
+ }
+ }
+
+ private QueryParametersParser() {
+
+ }
+
+ public static WriterParameters parseWriterParameters(final UriInfo info) {
+ final WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder();
+ if (info == null) {
+ return wpBuilder.build();
+ }
+
+ String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString());
+ if (!Strings.isNullOrEmpty(param) && !"unbounded".equals(param)) {
+ try {
+ final int depth = Integer.parseInt(param);
+ if (depth < 1) {
+ throw new RestconfDocumentedException(
+ new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + depth, null,
+ "The depth parameter must be an integer > 1 or \"unbounded\""));
+ }
+ wpBuilder.setDepth(depth);
+ } catch (final NumberFormatException e) {
+ throw new RestconfDocumentedException(e, new RestconfError(
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + e.getMessage(), null,
+ "The depth parameter must be an integer > 1 or \"unbounded\""));
+ }
+ }
+ param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString());
+ wpBuilder.setPrettyPrint("true".equals(param));
+ return wpBuilder.build();
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
new file mode 100644
index 0000000..5d7a840
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
+import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class RestCodec {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class);
+
+ private RestCodec() {
+ }
+
+ // FIXME: IllegalArgumentCodec is not quite accurate
+ public static IllegalArgumentCodec<Object, Object> from(final TypeDefinition<?> typeDefinition,
+ final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
+ return new ObjectCodec(typeDefinition, mountPoint, controllerContext);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final class ObjectCodec implements IllegalArgumentCodec<Object, Object> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ObjectCodec.class);
+
+ public static final IllegalArgumentCodec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();
+
+ private final ControllerContext controllerContext;
+ private final IllegalArgumentCodec instanceIdentifier;
+ private final IllegalArgumentCodec identityrefCodec;
+
+ private final TypeDefinition<?> type;
+
+ private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ this.controllerContext = controllerContext;
+ type = RestUtil.resolveBaseTypeFrom(typeDefinition);
+ if (type instanceof IdentityrefTypeDefinition) {
+ identityrefCodec = new IdentityrefCodecImpl(mountPoint, controllerContext);
+ } else {
+ identityrefCodec = null;
+ }
+ if (type instanceof InstanceIdentifierTypeDefinition) {
+ instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint, controllerContext);
+ } else {
+ instanceIdentifier = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy code")
+ public Object deserialize(final Object input) {
+ try {
+ if (type instanceof IdentityrefTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return identityrefCodec.deserialize(input);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "Value is not instance of IdentityrefTypeDefinition but is {}. "
+ + "Therefore NULL is used as translation of - {}",
+ input == null ? "null" : input.getClass(), String.valueOf(input));
+ }
+ // FIXME: this should be a hard error
+ return null;
+ } else if (type instanceof InstanceIdentifierTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return instanceIdentifier.deserialize(input);
+ } else {
+ final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+ controllerContext.getGlobalSchema());
+ return codec.deserialize((String) input);
+ }
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(type);
+ if (typeAwarecodec != null) {
+ if (input instanceof IdentityValuesDTO) {
+ return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
+ }
+ return typeAwarecodec.deserialize(String.valueOf(input));
+ } else {
+ LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ } catch (final ClassCastException e) { // TODO remove this catch when everyone use codecs
+ LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "legacy code")
+ public Object serialize(final Object input) {
+ try {
+ if (type instanceof IdentityrefTypeDefinition) {
+ return identityrefCodec.serialize(input);
+ } else if (type instanceof LeafrefTypeDefinition) {
+ return LEAFREF_DEFAULT_CODEC.serialize(input);
+ } else if (type instanceof InstanceIdentifierTypeDefinition) {
+ return instanceIdentifier.serialize(input);
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(type);
+ if (typeAwarecodec != null) {
+ return typeAwarecodec.serialize(input);
+ } else {
+ LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
+ return null;
+ }
+ }
+ } catch (final ClassCastException e) {
+ // FIXME: remove this catch when everyone use codecs
+ LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
+ // FIXME: this should be a hard error
+ return input;
+ }
+ }
+
+ }
+
+ public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
+ private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class);
+
+ private final DOMMountPoint mountPoint;
+ private final ControllerContext controllerContext;
+
+ public IdentityrefCodecImpl(final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
+ this.mountPoint = mountPoint;
+ this.controllerContext = controllerContext;
+ }
+
+ @Override
+ public IdentityValuesDTO serialize(final QName data) {
+ return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "See FIXME below")
+ public QName deserialize(final IdentityValuesDTO data) {
+ final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
+ controllerContext);
+ if (module == null) {
+ // FIXME: this should be a hard error
+ LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
+ LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
+ return null;
+ }
+
+ return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue());
+ }
+ }
+
+ public static class LeafrefCodecImpl implements LeafrefCodec<String> {
+
+ @Override
+ public String serialize(final Object data) {
+ return String.valueOf(data);
+ }
+
+ @Override
+ public Object deserialize(final String data) {
+ return data;
+ }
+
+ }
+
+ public static class InstanceIdentifierCodecImpl implements InstanceIdentifierCodec<IdentityValuesDTO> {
+ private static final Logger LOG = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class);
+
+ private final DOMMountPoint mountPoint;
+ private final ControllerContext controllerContext;
+
+ public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ this.mountPoint = mountPoint;
+ this.controllerContext = controllerContext;
+ }
+
+ @Override
+ public IdentityValuesDTO serialize(final YangInstanceIdentifier data) {
+ final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
+ for (final PathArgument pathArgument : data.getPathArguments()) {
+ final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
+ if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
+ final List<Predicate> predicates =
+ keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument).entrySet());
+ identityValue.setPredicates(predicates);
+ } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
+ final List<Predicate> predicates = new ArrayList<>();
+ final String value = String.valueOf(((NodeWithValue<?>) pathArgument).getValue());
+ predicates.add(new Predicate(null, value));
+ identityValue.setPredicates(predicates);
+ }
+ identityValuesDTO.add(identityValue);
+ }
+ return identityValuesDTO;
+ }
+
+ @SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", "NP_NONNULL_RETURN_VIOLATION" },
+ justification = "Unrecognised NullableDecl")
+ @Override
+ public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
+ final List<PathArgument> result = new ArrayList<>();
+ final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
+ final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
+ controllerContext);
+ if (module == null) {
+ LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
+ valueWithNamespace.getNamespace());
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(valueWithNamespace.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+
+ DataNodeContainer parentContainer = module;
+ final List<IdentityValue> identities = data.getValuesWithNamespaces();
+ for (int i = 0; i < identities.size(); i++) {
+ final IdentityValue identityValue = identities.get(i);
+ XMLNamespace validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint,
+ controllerContext);
+ final var found = ControllerContext.findInstanceDataChildByNameAndNamespace(
+ parentContainer, identityValue.getValue(), validNamespace);
+ if (found == null) {
+ LOG.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes());
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ final DataSchemaNode node = found.child;
+ final QName qName = node.getQName();
+ PathArgument pathArgument = null;
+ if (identityValue.getPredicates().isEmpty()) {
+ pathArgument = new NodeIdentifier(qName);
+ } else {
+ if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry
+ final Predicate leafListPredicate = identityValue.getPredicates().get(0);
+ if (!leafListPredicate.isLeafList()) {
+ LOG.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\"");
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ pathArgument = new NodeWithValue<>(qName, leafListPredicate.getValue());
+ } else if (node instanceof ListSchemaNode) { // predicates are keys of list
+ final DataNodeContainer listNode = (DataNodeContainer) node;
+ final Map<QName, Object> predicatesMap = new HashMap<>();
+ for (final Predicate predicate : identityValue.getPredicates()) {
+ validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint,
+ controllerContext);
+ final var listKey = ControllerContext
+ .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
+ validNamespace);
+ predicatesMap.put(listKey.child.getQName(), predicate.getValue());
+ }
+ pathArgument = NodeIdentifierWithPredicates.of(qName, predicatesMap);
+ } else {
+ LOG.info("Node {} is not List or Leaf-list.", node);
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ result.add(pathArgument);
+ if (i < identities.size() - 1) { // last element in instance-identifier can be other than
+ // DataNodeContainer
+ if (node instanceof DataNodeContainer) {
+ parentContainer = (DataNodeContainer) node;
+ } else {
+ LOG.info("Node {} isn't instance of DataNodeContainer", node);
+ LOG.info("Instance-identifier will be translated as NULL for data - {}",
+ String.valueOf(identityValue.getValue()));
+ // FIXME: this should be a hard error
+ return null;
+ }
+ }
+ }
+
+ return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
+ }
+
+ private static List<Predicate> keyValuesToPredicateList(final Set<Entry<QName, Object>> keyValues) {
+ final List<Predicate> result = new ArrayList<>();
+ for (final Entry<QName, Object> entry : keyValues) {
+ final QName qualifiedName = entry.getKey();
+ final Object value = entry.getValue();
+ result.add(new Predicate(qNameToIdentityValue(qualifiedName), String.valueOf(value)));
+ }
+ return result;
+ }
+
+ private static IdentityValue qNameToIdentityValue(final QName qualifiedName) {
+ if (qualifiedName != null) {
+ return new IdentityValue(qualifiedName.getNamespace().toString(), qualifiedName.getLocalName());
+ }
+ return null;
+ }
+ }
+
+ private static Module getModuleByNamespace(final String namespace, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ final XMLNamespace validNamespace = resolveValidNamespace(namespace, mountPoint, controllerContext);
+
+ Module module = null;
+ if (mountPoint != null) {
+ module = ControllerContext.findModuleByNamespace(mountPoint, validNamespace);
+ } else {
+ module = controllerContext.findModuleByNamespace(validNamespace);
+ }
+ if (module == null) {
+ LOG.info("Module for namespace {} was not found.", validNamespace);
+ return null;
+ }
+ return module;
+ }
+
+ private static XMLNamespace resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint,
+ final ControllerContext controllerContext) {
+ XMLNamespace validNamespace;
+ if (mountPoint != null) {
+ validNamespace = ControllerContext.findNamespaceByModuleName(mountPoint, namespace);
+ } else {
+ validNamespace = controllerContext.findNamespaceByModuleName(namespace);
+ }
+ if (validNamespace == null) {
+ validNamespace = XMLNamespace.of(namespace);
+ }
+
+ return validNamespace;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
new file mode 100644
index 0000000..b3a1a9c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
@@ -0,0 +1,1546 @@
+/*
+ * Copyright (c) 2014 - 2016 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicates;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.net.URI;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext.FoundChild;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
+import org.opendaylight.restconf.common.OperationsContent;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.util.OperationsResourceUtils;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.common.YangConstants;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
+import org.opendaylight.yangtools.yang.data.tree.api.ModifiedNodeDoesNotExistException;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public final class RestconfImpl implements RestconfService {
+ /**
+ * Notifications are served on port 8181.
+ */
+ private static final int NOTIFICATION_PORT = 8181;
+
+ private static final int CHAR_NOT_FOUND = -1;
+
+ private static final String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfImpl.class);
+
+ private static final LogicalDatastoreType DEFAULT_DATASTORE = LogicalDatastoreType.CONFIGURATION;
+
+ private static final XMLNamespace NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT =
+ XMLNamespace.of("urn:sal:restconf:event:subscription");
+
+ private static final String DATASTORE_PARAM_NAME = "datastore";
+
+ private static final String SCOPE_PARAM_NAME = "scope";
+
+ private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
+
+ private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
+
+ private static final String NETCONF_BASE_PAYLOAD_NAME = "data";
+
+ private static final QName NETCONF_BASE_QNAME = QName.create(QNameModule.create(XMLNamespace.of(NETCONF_BASE)),
+ NETCONF_BASE_PAYLOAD_NAME).intern();
+
+ private static final QNameModule SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT,
+ Revision.of("2014-07-08"));
+
+ private static final AugmentationIdentifier SAL_REMOTE_AUG_IDENTIFIER =
+ new AugmentationIdentifier(ImmutableSet.of(
+ QName.create(SAL_REMOTE_AUGMENT, "scope"), QName.create(SAL_REMOTE_AUGMENT, "datastore"),
+ QName.create(SAL_REMOTE_AUGMENT, "notification-output-type")));
+
+ public static final String DATA_SUBSCR = "data-change-event-subscription";
+ private static final String CREATE_DATA_SUBSCR = "create-" + DATA_SUBSCR;
+
+ public static final String NOTIFICATION_STREAM = "notification-stream";
+ private static final String CREATE_NOTIFICATION_STREAM = "create-" + NOTIFICATION_STREAM;
+
+ private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 4).appendLiteral('-')
+ .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-')
+ .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('T')
+ .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':')
+ .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+ .appendOffset("+HH:MM", "Z").toFormatter();
+
+ private final BrokerFacade broker;
+
+ private final ControllerContext controllerContext;
+
+ @Inject
+ public RestconfImpl(final BrokerFacade broker, final ControllerContext controllerContext) {
+ this.broker = broker;
+ this.controllerContext = controllerContext;
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use {@link #RestconfImpl(BrokerFacade, ControllerContext)} constructor instead.
+ */
+ @Deprecated
+ public static RestconfImpl newInstance(final BrokerFacade broker, final ControllerContext controllerContext) {
+ return new RestconfImpl(broker, controllerContext);
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var modules = QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final var modulesSchemaNode = stack.enterSchemaTree(modules);
+ checkState(modulesSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
+ moduleContainerBuilder.withChild(makeModuleMapNode(controllerContext.getAllModules()));
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, null),
+ moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ /**
+ * Valid only for mount point.
+ */
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ if (!identifier.contains(ControllerContext.MOUNT)) {
+ final String errMsg = "URI has bad format. If modules behind mount point should be showed,"
+ + " URI has to end with " + ControllerContext.MOUNT;
+ LOG.debug("{} for {}", errMsg, identifier);
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final InstanceIdentifierContext mountPointIdentifier =
+ controllerContext.toMountPointIdentifier(identifier);
+ final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
+ final MapNode mountPointModulesMap = makeModuleMapNode(controllerContext.getAllModules(mountPoint));
+
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var modules = QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final var modulesSchemaNode = stack.enterSchemaTree(modules);
+ checkState(modulesSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> moduleContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) modulesSchemaNode);
+ moduleContainerBuilder.withChild(mountPointModulesMap);
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, null),
+ moduleContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ final Entry<String, Revision> nameRev = getModuleNameAndRevision(requireNonNull(identifier));
+ final Module module;
+ final DOMMountPoint mountPoint;
+ if (identifier.contains(ControllerContext.MOUNT)) {
+ final InstanceIdentifierContext mountPointIdentifier =
+ controllerContext.toMountPointIdentifier(identifier);
+ mountPoint = mountPointIdentifier.getMountPoint();
+ module = controllerContext.findModuleByNameAndRevision(mountPoint, nameRev.getKey(),
+ nameRev.getValue());
+ } else {
+ mountPoint = null;
+ module = controllerContext.findModuleByNameAndRevision(nameRev.getKey(), nameRev.getValue());
+ }
+
+ if (module == null) {
+ LOG.debug("Module with name '{}' and revision '{}' was not found.", nameRev.getKey(), nameRev.getValue());
+ throw new RestconfDocumentedException("Module with name '" + nameRev.getKey() + "' and revision '"
+ + nameRev.getValue() + "' was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final Module restconfModule = getRestconfModule();
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ stack.enterSchemaTree(QName.create(restconf, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE));
+ stack.enterSchemaTree(QName.create(restconf, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE));
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack, mountPoint),
+ makeModuleMapNode(Set.of(module)), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ final Set<String> availableStreams = Notificator.getStreamNames();
+ final Module restconfModule = getRestconfModule();
+ final DataSchemaNode streamSchemaNode = controllerContext
+ .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
+ checkState(streamSchemaNode instanceof ListSchemaNode);
+
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listStreamsBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) streamSchemaNode);
+
+ for (final String streamName : availableStreams) {
+ listStreamsBuilder.withChild(toStreamEntryNode(streamName, streamSchemaNode));
+ }
+
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ final var restconf = QName.create(restconfModule.getQNameModule(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ stack.enterGrouping(restconf);
+ stack.enterSchemaTree(restconf);
+ final var streams = QName.create(restconf, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ final var streamsContainerSchemaNode = stack.enterSchemaTree(streams);
+ checkState(streamsContainerSchemaNode instanceof ContainerSchemaNode);
+
+ final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> streamsContainerBuilder =
+ SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) streamsContainerSchemaNode);
+ streamsContainerBuilder.withChild(listStreamsBuilder.build());
+
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack),
+ streamsContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ @Deprecated
+ public String getOperationsJSON() {
+ return OperationsContent.JSON.bodyFor(controllerContext.getGlobalSchema());
+ }
+
+ @Override
+ @Deprecated
+ public String getOperationsXML() {
+ return OperationsContent.XML.bodyFor(controllerContext.getGlobalSchema());
+ }
+
+ @Override
+ @Deprecated
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ if (!identifier.contains(ControllerContext.MOUNT)) {
+ final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to "
+ + " end with " + ControllerContext.MOUNT;
+ LOG.debug("{} for {}", errMsg, identifier);
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final InstanceIdentifierContext mountPointIdentifier = controllerContext.toMountPointIdentifier(identifier);
+ final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
+ final var entry = OperationsResourceUtils.contextForModelContext(modelContext(mountPoint), mountPoint);
+ return new NormalizedNodeContext(entry.getKey(), entry.getValue());
+ }
+
+ private Module getRestconfModule() {
+ final Module restconfModule = controllerContext.getRestconfModule();
+ if (restconfModule == null) {
+ LOG.debug("ietf-restconf module was not found.");
+ throw new RestconfDocumentedException("ietf-restconf module was not found.", ErrorType.APPLICATION,
+ ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+
+ return restconfModule;
+ }
+
+ private static Entry<String, Revision> getModuleNameAndRevision(final String identifier) {
+ final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
+ String moduleNameAndRevision = "";
+ if (mountIndex >= 0) {
+ moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length());
+ } else {
+ moduleNameAndRevision = identifier;
+ }
+
+ final Splitter splitter = Splitter.on('/').omitEmptyStrings();
+ final List<String> pathArgs = splitter.splitToList(moduleNameAndRevision);
+ if (pathArgs.size() < 2) {
+ LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
+ throw new RestconfDocumentedException(
+ "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
+ try {
+ return Map.entry(pathArgs.get(0), Revision.of(pathArgs.get(1)));
+ } catch (final DateTimeParseException e) {
+ LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' {}", identifier);
+ throw new RestconfDocumentedException("URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
+ }
+ }
+
+ @Override
+ public Object getRoot() {
+ return null;
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ if (payload == null) {
+ // no payload specified, reroute this to no payload invokeRpc implementation
+ return invokeRpc(identifier, uriInfo);
+ }
+
+ final SchemaNode schema = payload.getInstanceIdentifierContext().getSchemaNode();
+ final ListenableFuture<? extends DOMRpcResult> response;
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final NormalizedNode input = nonnullInput(schema, payload.getData());
+ final EffectiveModelContext schemaContext;
+
+ if (mountPoint != null) {
+ final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
+ if (mountRpcServices.isEmpty()) {
+ LOG.debug("Error: Rpc service is missing.");
+ throw new RestconfDocumentedException("Rpc service is missing.");
+ }
+ schemaContext = modelContext(mountPoint);
+ response = mountRpcServices.get().invokeRpc(schema.getQName(), input);
+ } else {
+ final XMLNamespace namespace = schema.getQName().getNamespace();
+ if (namespace.toString().equals(SAL_REMOTE_NAMESPACE)) {
+ if (identifier.contains(CREATE_DATA_SUBSCR)) {
+ response = invokeSalRemoteRpcSubscribeRPC(payload);
+ } else if (identifier.contains(CREATE_NOTIFICATION_STREAM)) {
+ response = invokeSalRemoteRpcNotifiStrRPC(payload);
+ } else {
+ final String msg = "Not supported operation";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg, ErrorType.RPC, ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+ } else {
+ response = broker.invokeRpc(schema.getQName(), input);
+ }
+ schemaContext = controllerContext.getGlobalSchema();
+ }
+
+ final DOMRpcResult result = checkRpcResponse(response);
+
+ final NormalizedNode resultData;
+ if (result != null && result.getResult() != null) {
+ resultData = result.getResult();
+ } else {
+ resultData = null;
+ }
+
+ if (resultData != null && ((ContainerNode) resultData).isEmpty()) {
+ throw new WebApplicationException(Response.Status.NO_CONTENT);
+ }
+
+ final var resultNodeSchema = (RpcDefinition) payload.getInstanceIdentifierContext().getSchemaNode();
+ return new NormalizedNodeContext(
+ InstanceIdentifierContext.ofRpcOutput(schemaContext, resultNodeSchema, mountPoint), resultData,
+ QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @SuppressFBWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE",
+ justification = "Looks like a false positive, see below FIXME")
+ private NormalizedNodeContext invokeRpc(final String identifier, final UriInfo uriInfo) {
+ final DOMMountPoint mountPoint;
+ final String identifierEncoded;
+ final EffectiveModelContext schemaContext;
+ if (identifier.contains(ControllerContext.MOUNT)) {
+ // mounted RPC call - look up mount instance.
+ final InstanceIdentifierContext mountPointId = controllerContext.toMountPointIdentifier(identifier);
+ mountPoint = mountPointId.getMountPoint();
+ schemaContext = modelContext(mountPoint);
+ final int startOfRemoteRpcName =
+ identifier.lastIndexOf(ControllerContext.MOUNT) + ControllerContext.MOUNT.length() + 1;
+ final String remoteRpcName = identifier.substring(startOfRemoteRpcName);
+ identifierEncoded = remoteRpcName;
+
+ } else if (identifier.indexOf('/') == CHAR_NOT_FOUND) {
+ identifierEncoded = identifier;
+ mountPoint = null;
+ schemaContext = controllerContext.getGlobalSchema();
+ } else {
+ LOG.debug("Identifier {} cannot contain slash character (/).", identifier);
+ throw new RestconfDocumentedException(String.format("Identifier %n%s%ncan\'t contain slash character (/).%n"
+ + "If slash is part of identifier name then use %%2F placeholder.", identifier), ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
+ final String identifierDecoded = ControllerContext.urlPathArgDecode(identifierEncoded);
+ final RpcDefinition rpc;
+ if (mountPoint == null) {
+ rpc = controllerContext.getRpcDefinition(identifierDecoded);
+ } else {
+ rpc = findRpc(modelContext(mountPoint), identifierDecoded);
+ }
+
+ if (rpc == null) {
+ LOG.debug("RPC {} does not exist.", identifierDecoded);
+ throw new RestconfDocumentedException("RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ if (!rpc.getInput().getChildNodes().isEmpty()) {
+ LOG.debug("No input specified for RPC {} with an input section", rpc);
+ throw new RestconfDocumentedException("No input specified for RPC " + rpc
+ + " with an input section defined", ErrorType.RPC, ErrorTag.MISSING_ELEMENT);
+ }
+
+ final ContainerNode input = defaultInput(rpc.getQName());
+ final ListenableFuture<? extends DOMRpcResult> response;
+ if (mountPoint != null) {
+ final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
+ if (mountRpcServices.isEmpty()) {
+ throw new RestconfDocumentedException("Rpc service is missing.");
+ }
+ response = mountRpcServices.get().invokeRpc(rpc.getQName(), input);
+ } else {
+ response = broker.invokeRpc(rpc.getQName(), input);
+ }
+
+ final NormalizedNode result = checkRpcResponse(response).getResult();
+ if (result != null && ((ContainerNode) result).isEmpty()) {
+ throw new WebApplicationException(Response.Status.NO_CONTENT);
+ }
+
+ // FIXME: in reference to the above @SupressFBWarnings: "mountPoint" reference here trips up SpotBugs, as it
+ // thinks it can only ever be null. Except it can very much be non-null. The core problem is the horrible
+ // structure of this code where we have a sh*tload of checks for mountpoint above and all over the
+ // codebase where all that difference should have been properly encapsulated.
+ //
+ // This is legacy code, so if anybody cares to do that refactor, feel free to contribute, but I am not
+ // doing that work.
+ final var iic = mountPoint == null ? InstanceIdentifierContext.ofLocalRpcOutput(schemaContext, rpc)
+ : InstanceIdentifierContext.ofMountPointRpcOutput(mountPoint, schemaContext, rpc);
+ return new NormalizedNodeContext(iic, result, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ private static @NonNull NormalizedNode nonnullInput(final SchemaNode rpc, final NormalizedNode input) {
+ return input != null ? input : defaultInput(rpc.getQName());
+ }
+
+ private static @NonNull ContainerNode defaultInput(final QName rpcName) {
+ return ImmutableNodes.containerNode(YangConstants.operationInputQName(rpcName.getModule()));
+ }
+
+ @SuppressWarnings("checkstyle:avoidHidingCauseException")
+ private static DOMRpcResult checkRpcResponse(final ListenableFuture<? extends DOMRpcResult> response) {
+ if (response == null) {
+ return null;
+ }
+ try {
+ final DOMRpcResult retValue = response.get();
+ if (retValue.getErrors().isEmpty()) {
+ return retValue;
+ }
+ LOG.debug("RpcError message {}", retValue.getErrors());
+ throw new RestconfDocumentedException("RpcError message", null, retValue.getErrors());
+ } catch (final InterruptedException e) {
+ final String errMsg = "The operation was interrupted while executing and did not complete.";
+ LOG.debug("Rpc Interrupt - {}", errMsg, e);
+ throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, e);
+ } catch (final ExecutionException e) {
+ LOG.debug("Execution RpcError: ", e);
+ Throwable cause = e.getCause();
+ if (cause == null) {
+ throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+ e);
+ }
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+
+ if (cause instanceof IllegalArgumentException) {
+ throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ } else if (cause instanceof DOMRpcImplementationNotAvailableException) {
+ throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION,
+ ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+ throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+ cause);
+ } catch (final CancellationException e) {
+ final String errMsg = "The operation was cancelled while executing.";
+ LOG.debug("Cancel RpcExecution: {}", errMsg, e);
+ throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION);
+ }
+ }
+
+ private static void validateInput(final SchemaNode inputSchema, final NormalizedNodeContext payload) {
+ if (inputSchema != null && payload.getData() == null) {
+ // expected a non null payload
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ } else if (inputSchema == null && payload.getData() != null) {
+ // did not expect any input
+ throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+
+ private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
+ final ContainerNode value = (ContainerNode) payload.getData();
+ final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+ final Optional<DataContainerChild> path =
+ value.findChildByArg(new NodeIdentifier(QName.create(rpcQName, "path")));
+ final Object pathValue = path.isPresent() ? path.get().body() : null;
+
+ if (!(pathValue instanceof YangInstanceIdentifier)) {
+ LOG.debug("Instance identifier {} was not normalized correctly", rpcQName);
+ throw new RestconfDocumentedException("Instance identifier was not normalized correctly",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+
+ final YangInstanceIdentifier pathIdentifier = (YangInstanceIdentifier) pathValue;
+ final String streamName;
+ NotificationOutputType outputType = null;
+ if (!pathIdentifier.isEmpty()) {
+ final String fullRestconfIdentifier =
+ DATA_SUBSCR + controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
+
+ LogicalDatastoreType datastore =
+ parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
+ datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
+
+ Scope scope = parseEnumTypeParameter(value, Scope.class, SCOPE_PARAM_NAME);
+ scope = scope == null ? Scope.BASE : scope;
+
+ outputType = parseEnumTypeParameter(value, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
+ outputType = outputType == null ? NotificationOutputType.XML : outputType;
+
+ streamName = Notificator
+ .createStreamNameFromUri(fullRestconfIdentifier + "/datastore=" + datastore + "/scope=" + scope);
+ } else {
+ streamName = CREATE_DATA_SUBSCR;
+ }
+
+ if (Strings.isNullOrEmpty(streamName)) {
+ LOG.debug("Path is empty or contains value node which is not Container or List built-in type at {}",
+ pathIdentifier);
+ throw new RestconfDocumentedException("Path is empty or contains value node which is not Container or List "
+ + "built-in type.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ if (!Notificator.existListenerFor(streamName)) {
+ Notificator.createListener(pathIdentifier, streamName, outputType, controllerContext);
+ }
+
+ return Futures.immediateFuture(new DefaultDOMRpcResult(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(rpcQName, "output")))
+ .withChild(ImmutableNodes.leafNode(QName.create(rpcQName, "stream-name"), streamName))
+ .build()));
+ }
+
+ private static RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
+ final String[] splittedIdentifier = identifierDecoded.split(":");
+ if (splittedIdentifier.length != 2) {
+ LOG.debug("{} could not be split to 2 parts (module:rpc name)", identifierDecoded);
+ throw new RestconfDocumentedException(identifierDecoded + " could not be split to 2 parts "
+ + "(module:rpc name)", ErrorType.APPLICATION, ErrorTag.INVALID_VALUE);
+ }
+ for (final Module module : schemaContext.getModules()) {
+ if (module.getName().equals(splittedIdentifier[0])) {
+ for (final RpcDefinition rpcDefinition : module.getRpcs()) {
+ if (rpcDefinition.getQName().getLocalName().equals(splittedIdentifier[1])) {
+ return rpcDefinition;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ boolean withDefaUsed = false;
+ String withDefa = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "with-defaults":
+ if (!withDefaUsed) {
+ withDefaUsed = true;
+ withDefa = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("With-defaults parameter can be used only once.");
+ }
+ break;
+ default:
+ LOG.info("Unknown key : {}.", entry.getKey());
+ break;
+ }
+ }
+
+ // TODO: this flag is always ignored
+ boolean tagged = false;
+ if (withDefaUsed) {
+ if ("report-all-tagged".equals(withDefa)) {
+ tagged = true;
+ withDefa = null;
+ }
+ if ("report-all".equals(withDefa)) {
+ withDefa = null;
+ }
+ }
+
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ NormalizedNode data = null;
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+ if (mountPoint != null) {
+ data = broker.readConfigurationData(mountPoint, normalizedII, withDefa);
+ } else {
+ data = broker.readConfigurationData(normalizedII, withDefa);
+ }
+ if (data == null) {
+ throw dataMissing(identifier);
+ }
+ return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ NormalizedNode data = null;
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+ if (mountPoint != null) {
+ data = broker.readOperationalData(mountPoint, normalizedII);
+ } else {
+ data = broker.readOperationalData(normalizedII);
+ }
+ if (data == null) {
+ throw dataMissing(identifier);
+ }
+ return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
+ }
+
+ private static RestconfDocumentedException dataMissing(final String identifier) {
+ LOG.debug("Request could not be completed because the relevant data model content does not exist {}",
+ identifier);
+ return new RestconfDocumentedException("Request could not be completed because the relevant data model content "
+ + "does not exist", ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
+ }
+
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ boolean insertUsed = false;
+ boolean pointUsed = false;
+ String insert = null;
+ String point = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insertUsed) {
+ insertUsed = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!pointUsed) {
+ pointUsed = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+
+ if (pointUsed && !insertUsed) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
+ requireNonNull(identifier);
+
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+
+ validateInput(iiWithData.getSchemaNode(), payload);
+ validateTopLevelNodeName(payload, iiWithData.getInstanceIdentifier());
+ validateListKeysEqualityInPayloadAndUri(payload);
+
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ /*
+ * There is a small window where another write transaction could be
+ * updating the same data simultaneously and we get an
+ * OptimisticLockFailedException. This error is likely transient and The
+ * WriteTransaction#submit API docs state that a retry will likely
+ * succeed. So we'll try again if that scenario occurs. If it fails a
+ * third time then it probably will never succeed so we'll fail in that
+ * case.
+ *
+ * By retrying we're attempting to hide the internal implementation of
+ * the data store and how it handles concurrent updates from the
+ * restconf client. The client has instructed us to put the data and we
+ * should make every effort to do so without pushing optimistic lock
+ * failures back to the client and forcing them to handle it via retry
+ * (and having to document the behavior).
+ */
+ PutResult result = null;
+ int tries = 2;
+ while (true) {
+ if (mountPoint != null) {
+
+ result = broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData(), insert,
+ point);
+ } else {
+ result = broker.commitConfigurationDataPut(controllerContext.getGlobalSchema(), normalizedII,
+ payload.getData(), insert, point);
+ }
+
+ try {
+ result.getFutureOfPutData().get();
+ } catch (final InterruptedException e) {
+ LOG.debug("Update failed for {}", identifier, e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ final TransactionCommitFailedException failure = Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class);
+ if (failure instanceof OptimisticLockFailedException) {
+ if (--tries <= 0) {
+ LOG.debug("Got OptimisticLockFailedException on last try - failing {}", identifier);
+ throw new RestconfDocumentedException(e.getMessage(), e, failure.getErrorList());
+ }
+
+ LOG.debug("Got OptimisticLockFailedException - trying again {}", identifier);
+ continue;
+ }
+
+ LOG.debug("Update failed for {}", identifier, e);
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), failure);
+ }
+
+ return Response.status(result.getStatus()).build();
+ }
+ }
+
+ private static void validateTopLevelNodeName(final NormalizedNodeContext node,
+ final YangInstanceIdentifier identifier) {
+
+ final String payloadName = node.getData().getIdentifier().getNodeType().getLocalName();
+
+ // no arguments
+ if (identifier.isEmpty()) {
+ // no "data" payload
+ if (!node.getData().getIdentifier().getNodeType().equals(NETCONF_BASE_QNAME)) {
+ throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ // any arguments
+ } else {
+ final String identifierName = identifier.getLastPathArgument().getNodeType().getLocalName();
+ if (!payloadName.equals(identifierName)) {
+ throw new RestconfDocumentedException(
+ "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Validates whether keys in {@code payload} are equal to values of keys in
+ * {@code iiWithData} for list schema node.
+ *
+ * @throws RestconfDocumentedException
+ * if key values or key count in payload and URI isn't equal
+ *
+ */
+ private static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
+ checkArgument(payload != null);
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+ final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
+ final SchemaNode schemaNode = iiWithData.getSchemaNode();
+ final NormalizedNode data = payload.getData();
+ if (schemaNode instanceof ListSchemaNode) {
+ final List<QName> keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition();
+ if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) {
+ final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap();
+ isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
+ final List<QName> keyDefinitions) {
+
+ final Map<QName, Object> mutableCopyUriKeyValues = new HashMap<>(uriKeyValues);
+ for (final QName keyDefinition : keyDefinitions) {
+ final Object uriKeyValue = RestconfDocumentedException.throwIfNull(
+ // should be caught during parsing URI to InstanceIdentifier
+ mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
+ "Missing key %s in URI.", keyDefinition);
+
+ final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition);
+
+ if (!Objects.deepEquals(uriKeyValue, dataKeyValue)) {
+ final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName()
+ + "' specified in the URI doesn't match the value '" + dataKeyValue
+ + "' specified in the message body. ";
+ throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ }
+ }
+
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return createConfigurationData(payload, uriInfo);
+ }
+
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ if (payload == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ boolean insertUsed = false;
+ boolean pointUsed = false;
+ String insert = null;
+ String point = null;
+
+ if (uriInfo != null) {
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insertUsed) {
+ insertUsed = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!pointUsed) {
+ pointUsed = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+ }
+
+ if (pointUsed && !insertUsed) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
+ FluentFuture<? extends CommitInfo> future;
+ if (mountPoint != null) {
+ future = broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData(), insert,
+ point);
+ } else {
+ future = broker.commitConfigurationDataPost(controllerContext.getGlobalSchema(), normalizedII,
+ payload.getData(), insert, point);
+ }
+
+ try {
+ future.get();
+ } catch (final InterruptedException e) {
+ LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ LOG.info("Error creating data {}", uriInfo != null ? uriInfo.getPath() : "", e);
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class));
+ }
+
+ LOG.trace("Successfuly created data.");
+
+ final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
+ // FIXME: Provide path to result.
+ final URI location = resolveLocation(uriInfo, "", mountPoint, normalizedII);
+ if (location != null) {
+ responseBuilder.location(location);
+ }
+ return responseBuilder.build();
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint,
+ final YangInstanceIdentifier normalizedII) {
+ if (uriInfo == null) {
+ // This is null if invoked internally
+ return null;
+ }
+
+ final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("config");
+ try {
+ uriBuilder.path(controllerContext.toFullRestconfIdentifier(normalizedII, mountPoint));
+ } catch (final Exception e) {
+ LOG.info("Location for instance identifier {} was not created", normalizedII, e);
+ return null;
+ }
+ return uriBuilder.build();
+ }
+
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ final InstanceIdentifierContext iiWithData = controllerContext.toInstanceIdentifier(identifier);
+ final DOMMountPoint mountPoint = iiWithData.getMountPoint();
+ final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+
+ final FluentFuture<? extends CommitInfo> future;
+ if (mountPoint != null) {
+ future = broker.commitConfigurationDataDelete(mountPoint, normalizedII);
+ } else {
+ future = broker.commitConfigurationDataDelete(normalizedII);
+ }
+
+ try {
+ future.get();
+ } catch (final InterruptedException e) {
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ } catch (final ExecutionException e) {
+ final Optional<Throwable> searchedException = Iterables.tryFind(Throwables.getCausalChain(e),
+ Predicates.instanceOf(ModifiedNodeDoesNotExistException.class)).toJavaUtil();
+ if (searchedException.isPresent()) {
+ throw new RestconfDocumentedException("Data specified for delete doesn't exist.", ErrorType.APPLICATION,
+ ErrorTag.DATA_MISSING, e);
+ }
+
+ throw RestconfDocumentedException.decodeAndThrow(e.getMessage(), Throwables.getCauseAs(e,
+ TransactionCommitFailedException.class));
+ }
+
+ return Response.status(Status.OK).build();
+ }
+
+ /**
+ * Subscribes to some path in schema context (stream) to listen on changes
+ * on this stream.
+ *
+ * <p>
+ * Additional parameters for subscribing to stream are loaded via rpc input
+ * parameters:
+ * <ul>
+ * <li>datastore - default CONFIGURATION (other values of
+ * {@link LogicalDatastoreType} enum type)</li>
+ * <li>scope - default BASE (other values of {@link Scope})</li>
+ * </ul>
+ */
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ boolean startTimeUsed = false;
+ boolean stopTimeUsed = false;
+ Instant start = Instant.now();
+ Instant stop = null;
+ boolean filterUsed = false;
+ String filter = null;
+ boolean leafNodesOnlyUsed = false;
+ boolean leafNodesOnly = false;
+ boolean skipNotificationDataUsed = false;
+ boolean skipNotificationData = false;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "start-time":
+ if (!startTimeUsed) {
+ startTimeUsed = true;
+ start = parseDateFromQueryParam(entry);
+ } else {
+ throw new RestconfDocumentedException("Start-time parameter can be used only once.");
+ }
+ break;
+ case "stop-time":
+ if (!stopTimeUsed) {
+ stopTimeUsed = true;
+ stop = parseDateFromQueryParam(entry);
+ } else {
+ throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
+ }
+ break;
+ case "filter":
+ if (!filterUsed) {
+ filterUsed = true;
+ filter = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Filter parameter can be used only once.");
+ }
+ break;
+ case "odl-leaf-nodes-only":
+ if (!leafNodesOnlyUsed) {
+ leafNodesOnlyUsed = true;
+ leafNodesOnly = Boolean.parseBoolean(entry.getValue().iterator().next());
+ } else {
+ throw new RestconfDocumentedException("Odl-leaf-nodes-only parameter can be used only once.");
+ }
+ break;
+ case "odl-skip-notification-data":
+ if (!skipNotificationDataUsed) {
+ skipNotificationDataUsed = true;
+ skipNotificationData = Boolean.parseBoolean(entry.getValue().iterator().next());
+ } else {
+ throw new RestconfDocumentedException(
+ "Odl-skip-notification-data parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey());
+ }
+ }
+ if (!startTimeUsed && stopTimeUsed) {
+ throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
+ }
+ URI response = null;
+ if (identifier.contains(DATA_SUBSCR)) {
+ response = dataSubs(identifier, uriInfo, start, stop, filter, leafNodesOnly, skipNotificationData);
+ } else if (identifier.contains(NOTIFICATION_STREAM)) {
+ response = notifStream(identifier, uriInfo, start, stop, filter);
+ }
+
+ if (response != null) {
+ // prepare node with value of location
+
+ final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi");
+ final QName locationQName = QName.create(qnameBase, "location");
+
+ final var stack = SchemaInferenceStack.of(controllerContext.getGlobalSchema());
+ stack.enterSchemaTree(qnameBase);
+ stack.enterSchemaTree(locationQName);
+
+ // prepare new header with location
+ return new NormalizedNodeContext(InstanceIdentifierContext.ofStack(stack),
+ ImmutableNodes.leafNode(locationQName, response.toString()), ImmutableMap.of("Location", response));
+ }
+
+ final String msg = "Bad type of notification of sal-remote";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg);
+ }
+
+ private static Instant parseDateFromQueryParam(final Entry<String, List<String>> entry) {
+ final DateAndTime event = new DateAndTime(entry.getValue().iterator().next());
+ final String value = event.getValue();
+ final TemporalAccessor p;
+ try {
+ p = FORMATTER.parse(value);
+ } catch (final DateTimeParseException e) {
+ throw new RestconfDocumentedException("Cannot parse of value in date: " + value, e);
+ }
+ return Instant.from(p);
+ }
+
+ /**
+ * Register notification listener by stream name.
+ *
+ * @param identifier
+ * stream name
+ * @param uriInfo
+ * uriInfo
+ * @param stop
+ * stop-time of getting notification
+ * @param start
+ * start-time of getting notification
+ * @param filter
+ * indicate which subset of all possible events are of interest
+ * @return {@link URI} of location
+ */
+ private URI notifStream(final String identifier, final UriInfo uriInfo, final Instant start,
+ final Instant stop, final String filter) {
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+ if (Strings.isNullOrEmpty(streamName)) {
+ throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (listeners == null || listeners.isEmpty()) {
+ throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ for (final NotificationListenerAdapter listener : listeners) {
+ broker.registerToListenNotification(listener);
+ listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), false, false);
+ }
+
+ final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+
+ final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
+ final int notificationPort = webSocketServerInstance.getPort();
+
+
+ final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
+
+ return uriToWebsocketServerBuilder.replacePath(streamName).build();
+ }
+
+ private static String getWsScheme(final UriInfo uriInfo) {
+ URI uri = uriInfo.getAbsolutePath();
+ if (uri == null) {
+ return "ws";
+ }
+ String subscriptionScheme = uri.getScheme().toLowerCase(Locale.ROOT);
+ return subscriptionScheme.equals("https") ? "wss" : "ws";
+ }
+
+ /**
+ * Register data change listener by stream name.
+ *
+ * @param identifier
+ * stream name
+ * @param uriInfo
+ * uri info
+ * @param stop
+ * start-time of getting notification
+ * @param start
+ * stop-time of getting notification
+ * @param filter
+ * indicate which subset of all possible events are of interest
+ * @return {@link URI} of location
+ */
+ private URI dataSubs(final String identifier, final UriInfo uriInfo, final Instant start, final Instant stop,
+ final String filter, final boolean leafNodesOnly, final boolean skipNotificationData) {
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+ if (Strings.isNullOrEmpty(streamName)) {
+ throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+ if (listener == null) {
+ throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT);
+ }
+ listener.setQueryParams(start, Optional.ofNullable(stop), Optional.ofNullable(filter), leafNodesOnly,
+ skipNotificationData);
+
+ final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
+ final LogicalDatastoreType datastore =
+ parserURIEnumParameter(LogicalDatastoreType.class, paramToValues.get(DATASTORE_PARAM_NAME));
+ if (datastore == null) {
+ throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /datastore=)",
+ ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+ final Scope scope = parserURIEnumParameter(Scope.class, paramToValues.get(SCOPE_PARAM_NAME));
+ if (scope == null) {
+ throw new RestconfDocumentedException("Stream name doesn't contains datastore value (pattern /scope=)",
+ ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+
+ broker.registerToListenDataChanges(datastore, scope, listener);
+
+ final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+
+ final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance(NOTIFICATION_PORT);
+ final int notificationPort = webSocketServerInstance.getPort();
+
+ final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme(getWsScheme(uriInfo));
+
+ return uriToWebsocketServerBuilder.replacePath(streamName).build();
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext context,
+ final UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ try {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (final Exception e) {
+ LOG.debug("Patch transaction failed", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext context, @Context final UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ try {
+ return broker.patchConfigurationDataWithinTransaction(context);
+ } catch (final Exception e) {
+ LOG.debug("Patch transaction failed", e);
+ throw new RestconfDocumentedException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Load parameter for subscribing to stream from input composite node.
+ *
+ * @param value
+ * contains value
+ * @return enum object if its string value is equal to {@code paramName}. In
+ * other cases null.
+ */
+ private static <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
+ final String paramName) {
+ final Optional<DataContainerChild> optAugNode = value.findChildByArg(SAL_REMOTE_AUG_IDENTIFIER);
+ if (optAugNode.isEmpty()) {
+ return null;
+ }
+ final DataContainerChild augNode = optAugNode.get();
+ if (!(augNode instanceof AugmentationNode)) {
+ return null;
+ }
+ final Optional<DataContainerChild> enumNode = ((AugmentationNode) augNode).findChildByArg(
+ new NodeIdentifier(QName.create(SAL_REMOTE_AUGMENT, paramName)));
+ if (enumNode.isEmpty()) {
+ return null;
+ }
+ final Object rawValue = enumNode.get().body();
+ if (!(rawValue instanceof String)) {
+ return null;
+ }
+
+ return resolveAsEnum(classDescriptor, (String) rawValue);
+ }
+
+ /**
+ * Checks whether {@code value} is one of the string representation of
+ * enumeration {@code classDescriptor}.
+ *
+ * @return enum object if string value of {@code classDescriptor}
+ * enumeration is equal to {@code value}. Other cases null.
+ */
+ private static <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
+ if (Strings.isNullOrEmpty(value)) {
+ return null;
+ }
+ return resolveAsEnum(classDescriptor, value);
+ }
+
+ private static <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
+ final T[] enumConstants = classDescriptor.getEnumConstants();
+ if (enumConstants != null) {
+ for (final T enm : classDescriptor.getEnumConstants()) {
+ if (((Enum<?>) enm).name().equals(value)) {
+ return enm;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Map<String, String> resolveValuesFromUri(final String uri) {
+ final Map<String, String> result = new HashMap<>();
+ final String[] tokens = uri.split("/");
+ for (int i = 1; i < tokens.length; i++) {
+ final String[] parameterTokens = tokens[i].split("=");
+ if (parameterTokens.length == 2) {
+ result.put(parameterTokens[0], parameterTokens[1]);
+ }
+ }
+ return result;
+ }
+
+ private MapNode makeModuleMapNode(final Collection<? extends Module> modules) {
+ requireNonNull(modules);
+ final Module restconfModule = getRestconfModule();
+ final DataSchemaNode moduleSchemaNode = controllerContext
+ .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
+ checkState(moduleSchemaNode instanceof ListSchemaNode);
+
+ final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listModuleBuilder =
+ SchemaAwareBuilders.mapBuilder((ListSchemaNode) moduleSchemaNode);
+
+ for (final Module module : modules) {
+ listModuleBuilder.withChild(toModuleEntryNode(module, moduleSchemaNode));
+ }
+ return listModuleBuilder.build();
+ }
+
+ private static MapEntryNode toModuleEntryNode(final Module module, final DataSchemaNode moduleSchemaNode) {
+ checkArgument(moduleSchemaNode instanceof ListSchemaNode,
+ "moduleSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listModuleSchemaNode = (ListSchemaNode) moduleSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> moduleNodeValues =
+ SchemaAwareBuilders.mapEntryBuilder(listModuleSchemaNode);
+
+ var instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "name");
+ final LeafSchemaNode nameSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ moduleNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(nameSchemaNode).withValue(module.getName()).build());
+
+ final QNameModule qNameModule = module.getQNameModule();
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "revision");
+ final LeafSchemaNode revisionSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ final Optional<Revision> revision = qNameModule.getRevision();
+ moduleNodeValues.withChild(SchemaAwareBuilders.leafBuilder(revisionSchemaNode)
+ .withValue(revision.map(Revision::toString).orElse("")).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "namespace");
+ final LeafSchemaNode namespaceSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ moduleNodeValues.withChild(SchemaAwareBuilders.leafBuilder(namespaceSchemaNode)
+ .withValue(qNameModule.getNamespace().toString()).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listModuleSchemaNode, "feature");
+ final LeafListSchemaNode featureSchemaNode = getFirst(instanceDataChildrenByName, LeafListSchemaNode.class);
+ final ListNodeBuilder<Object, SystemLeafSetNode<Object>> featuresBuilder =
+ SchemaAwareBuilders.leafSetBuilder(featureSchemaNode);
+ for (final FeatureDefinition feature : module.getFeatures()) {
+ featuresBuilder.withChild(SchemaAwareBuilders.leafSetEntryBuilder(featureSchemaNode)
+ .withValue(feature.getQName().getLocalName()).build());
+ }
+ moduleNodeValues.withChild(featuresBuilder.build());
+
+ return moduleNodeValues.build();
+ }
+
+ protected MapEntryNode toStreamEntryNode(final String streamName, final DataSchemaNode streamSchemaNode) {
+ checkArgument(streamSchemaNode instanceof ListSchemaNode,
+ "streamSchemaNode has to be of type ListSchemaNode");
+ final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) streamSchemaNode;
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamNodeValues =
+ SchemaAwareBuilders.mapEntryBuilder(listStreamSchemaNode);
+
+ var instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "name");
+ final LeafSchemaNode nameSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(nameSchemaNode).withValue(streamName).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "description");
+ final LeafSchemaNode descriptionSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder(descriptionSchemaNode)
+ .withValue("DESCRIPTION_PLACEHOLDER")
+ .build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-support");
+ final LeafSchemaNode replaySupportSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(SchemaAwareBuilders.leafBuilder(replaySupportSchemaNode)
+ .withValue(Boolean.TRUE).build());
+
+ instanceDataChildrenByName =
+ ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "replay-log-creation-time");
+ final LeafSchemaNode replayLogCreationTimeSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(replayLogCreationTimeSchemaNode).withValue("").build());
+
+ instanceDataChildrenByName = ControllerContext.findInstanceDataChildrenByName(listStreamSchemaNode, "events");
+ final LeafSchemaNode eventsSchemaNode = getFirstLeaf(instanceDataChildrenByName);
+ streamNodeValues.withChild(
+ SchemaAwareBuilders.leafBuilder(eventsSchemaNode).withValue(Empty.value()).build());
+
+ return streamNodeValues.build();
+ }
+
+ /**
+ * Prepare stream for notification.
+ *
+ * @param payload
+ * contains list of qnames of notifications
+ * @return - checked future object
+ */
+ private ListenableFuture<DOMRpcResult> invokeSalRemoteRpcNotifiStrRPC(final NormalizedNodeContext payload) {
+ final ContainerNode data = (ContainerNode) payload.getData();
+ LeafSetNode leafSet = null;
+ String outputType = "XML";
+ for (final DataContainerChild dataChild : data.body()) {
+ if (dataChild instanceof LeafSetNode) {
+ leafSet = (LeafSetNode) dataChild;
+ } else if (dataChild instanceof AugmentationNode) {
+ outputType = (String) ((AugmentationNode) dataChild).body().iterator().next().body();
+ }
+ }
+
+ final Collection<LeafSetEntryNode<?>> entryNodes = leafSet.body();
+ final List<Absolute> paths = new ArrayList<>();
+
+ StringBuilder streamNameBuilder = new StringBuilder(CREATE_NOTIFICATION_STREAM).append('/');
+ final Iterator<LeafSetEntryNode<?>> iterator = entryNodes.iterator();
+ while (iterator.hasNext()) {
+ final QName valueQName = QName.create((String) iterator.next().body());
+ final XMLNamespace namespace = valueQName.getModule().getNamespace();
+ final Module module = controllerContext.findModuleByNamespace(namespace);
+ checkNotNull(module, "Module for namespace %s does not exist", namespace);
+ NotificationDefinition notifiDef = null;
+ for (final NotificationDefinition notification : module.getNotifications()) {
+ if (notification.getQName().equals(valueQName)) {
+ notifiDef = notification;
+ break;
+ }
+ }
+ final String moduleName = module.getName();
+ if (notifiDef == null) {
+ throw new IllegalArgumentException("Notification " + valueQName + " does not exist in module "
+ + moduleName);
+ }
+
+ paths.add(Absolute.of(notifiDef.getQName()));
+ streamNameBuilder.append(moduleName).append(':').append(valueQName.getLocalName());
+ if (iterator.hasNext()) {
+ streamNameBuilder.append(',');
+ }
+ }
+
+ final String streamName = streamNameBuilder.toString();
+ final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+
+ if (!Notificator.existNotificationListenerFor(streamName)) {
+ Notificator.createNotificationListener(paths, streamName, outputType, controllerContext);
+ }
+
+ return Futures.immediateFuture(new DefaultDOMRpcResult(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(rpcQName, "output")))
+ .withChild(ImmutableNodes.leafNode(QName.create(rpcQName, "notification-stream-identifier"), streamName))
+ .build()));
+ }
+
+ private static LeafSchemaNode getFirstLeaf(final List<FoundChild> children) {
+ return getFirst(children, LeafSchemaNode.class);
+ }
+
+ private static <T extends DataSchemaNode> T getFirst(final List<FoundChild> children, final Class<T> expected) {
+ checkState(!children.isEmpty());
+ final var first = children.get(0);
+ checkState(expected.isInstance(first.child));
+ return expected.cast(first.child);
+ }
+
+ private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
+ return mountPoint.getService(DOMSchemaService.class)
+ .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
+ .orElse(null);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java
new file mode 100644
index 0000000..5b6608e
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.math.BigInteger;
+import org.opendaylight.controller.md.sal.common.util.jmx.AbstractMXBean;
+import org.opendaylight.netconf.sal.rest.api.RestConnector;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Config;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Delete;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Get;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Operational;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Post;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Put;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.RestConnectorRuntimeMXBean;
+import org.opendaylight.netconf.sal.restconf.impl.jmx.Rpcs;
+import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
+
+public class RestconfProviderImpl extends AbstractMXBean
+ implements AutoCloseable, RestConnector, RestConnectorRuntimeMXBean {
+ private final IpAddress websocketAddress;
+ private final PortNumber websocketPort;
+ private final StatisticsRestconfServiceWrapper stats;
+ private Thread webSocketServerThread;
+
+ public RestconfProviderImpl(final StatisticsRestconfServiceWrapper stats, final IpAddress websocketAddress,
+ final PortNumber websocketPort) {
+ super("Draft02ProviderStatistics", "restconf-connector", null);
+ this.stats = requireNonNull(stats);
+ this.websocketAddress = requireNonNull(websocketAddress);
+ this.websocketPort = requireNonNull(websocketPort);
+ }
+
+ public void start() {
+ this.webSocketServerThread = new Thread(WebSocketServer.createInstance(
+ websocketAddress.stringValue(), websocketPort.getValue().toJava()));
+ this.webSocketServerThread.setName("Web socket server on port " + websocketPort);
+ this.webSocketServerThread.start();
+
+ registerMBean();
+ }
+
+ @Override
+ public void close() {
+ WebSocketServer.destroyInstance();
+ if (this.webSocketServerThread != null) {
+ this.webSocketServerThread.interrupt();
+ }
+
+ unregisterMBean();
+ }
+
+ @Override
+ public Config getConfig() {
+ final Config config = new Config();
+
+ final Get get = new Get();
+ get.setReceivedRequests(this.stats.getConfigGet());
+ get.setSuccessfulResponses(this.stats.getSuccessGetConfig());
+ get.setFailedResponses(this.stats.getFailureGetConfig());
+ config.setGet(get);
+
+ final Post post = new Post();
+ post.setReceivedRequests(this.stats.getConfigPost());
+ post.setSuccessfulResponses(this.stats.getSuccessPost());
+ post.setFailedResponses(this.stats.getFailurePost());
+ config.setPost(post);
+
+ final Put put = new Put();
+ put.setReceivedRequests(this.stats.getConfigPut());
+ put.setSuccessfulResponses(this.stats.getSuccessPut());
+ put.setFailedResponses(this.stats.getFailurePut());
+ config.setPut(put);
+
+ final Delete delete = new Delete();
+ delete.setReceivedRequests(this.stats.getConfigDelete());
+ delete.setSuccessfulResponses(this.stats.getSuccessDelete());
+ delete.setFailedResponses(this.stats.getFailureDelete());
+ config.setDelete(delete);
+
+ return config;
+ }
+
+ @Override
+ public Operational getOperational() {
+ final BigInteger opGet = this.stats.getOperationalGet();
+ final Operational operational = new Operational();
+ final Get get = new Get();
+ get.setReceivedRequests(opGet);
+ get.setSuccessfulResponses(this.stats.getSuccessGetOperational());
+ get.setFailedResponses(this.stats.getFailureGetOperational());
+ operational.setGet(get);
+ return operational;
+ }
+
+ @Override
+ public Rpcs getRpcs() {
+ final BigInteger rpcInvoke = this.stats.getRpc();
+ final Rpcs rpcs = new Rpcs();
+ rpcs.setReceivedRequests(rpcInvoke);
+ return rpcs;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java
new file mode 100644
index 0000000..afe4be0
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/StatisticsRestconfServiceWrapper.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl;
+
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+@Singleton
+public final class StatisticsRestconfServiceWrapper implements RestconfService {
+
+ AtomicLong operationalGet = new AtomicLong();
+ AtomicLong configGet = new AtomicLong();
+ AtomicLong rpc = new AtomicLong();
+ AtomicLong configPost = new AtomicLong();
+ AtomicLong configPut = new AtomicLong();
+ AtomicLong configDelete = new AtomicLong();
+ AtomicLong successGetConfig = new AtomicLong();
+ AtomicLong successGetOperational = new AtomicLong();
+ AtomicLong successPost = new AtomicLong();
+ AtomicLong successPut = new AtomicLong();
+ AtomicLong successDelete = new AtomicLong();
+ AtomicLong failureGetConfig = new AtomicLong();
+ AtomicLong failureGetOperational = new AtomicLong();
+ AtomicLong failurePost = new AtomicLong();
+ AtomicLong failurePut = new AtomicLong();
+ AtomicLong failureDelete = new AtomicLong();
+
+ private final RestconfService delegate;
+
+ @Inject
+ public StatisticsRestconfServiceWrapper(final RestconfImpl delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * Factory method.
+ *
+ * @deprecated Just use {@link #StatisticsRestconfServiceWrapper(RestconfImpl)} constructor instead.
+ */
+ @Deprecated
+ public static StatisticsRestconfServiceWrapper newInstance(RestconfImpl delegate) {
+ return new StatisticsRestconfServiceWrapper(delegate);
+ }
+
+ @Override
+ public Object getRoot() {
+ return this.delegate.getRoot();
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final UriInfo uriInfo) {
+ return this.delegate.getModules(uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getModules(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getModule(identifier, uriInfo);
+ }
+
+ @Override
+ public String getOperationsJSON() {
+ return this.delegate.getOperationsJSON();
+ }
+
+ @Override
+ public String getOperationsXML() {
+ return this.delegate.getOperationsXML();
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.getOperations(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.rpc.incrementAndGet();
+ return this.delegate.invokeRpc(identifier, payload, uriInfo);
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
+ this.configGet.incrementAndGet();
+ NormalizedNodeContext normalizedNodeContext = null;
+ try {
+ normalizedNodeContext = this.delegate.readConfigurationData(identifier, uriInfo);
+ if (normalizedNodeContext.getData() != null) {
+ this.successGetConfig.incrementAndGet();
+ } else {
+ this.failureGetConfig.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureGetConfig.incrementAndGet();
+ throw e;
+ }
+ return normalizedNodeContext;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
+ this.operationalGet.incrementAndGet();
+ NormalizedNodeContext normalizedNodeContext = null;
+ try {
+ normalizedNodeContext = this.delegate.readOperationalData(identifier, uriInfo);
+ if (normalizedNodeContext.getData() != null) {
+ this.successGetOperational.incrementAndGet();
+ } else {
+ this.failureGetOperational.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureGetOperational.incrementAndGet();
+ throw e;
+ }
+ return normalizedNodeContext;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.configPut.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.updateConfigurationData(identifier, payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPut.incrementAndGet();
+ } else {
+ this.failurePut.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePut.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ this.configPost.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.createConfigurationData(identifier, payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPost.incrementAndGet();
+ } else {
+ this.failurePost.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePost.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ this.configPost.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.createConfigurationData(payload, uriInfo);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successPost.incrementAndGet();
+ } else {
+ this.failurePost.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failurePost.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ @Override
+ public Response deleteConfigurationData(final String identifier) {
+ this.configDelete.incrementAndGet();
+ Response response = null;
+ try {
+ response = this.delegate.deleteConfigurationData(identifier);
+ if (response.getStatus() == Status.OK.getStatusCode()) {
+ this.successDelete.incrementAndGet();
+ } else {
+ this.failureDelete.incrementAndGet();
+ }
+ } catch (final Exception e) {
+ this.failureDelete.incrementAndGet();
+ throw e;
+ }
+ return response;
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ return this.delegate.subscribeToStream(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
+ return this.delegate.getAvailableStreams(uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final String identifier, final PatchContext payload,
+ final UriInfo uriInfo) {
+ return this.delegate.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchConfigurationData(final PatchContext payload, final UriInfo uriInfo) {
+ return this.delegate.patchConfigurationData(payload, uriInfo);
+ }
+
+ public BigInteger getConfigDelete() {
+ return BigInteger.valueOf(this.configDelete.get());
+ }
+
+ public BigInteger getConfigGet() {
+ return BigInteger.valueOf(this.configGet.get());
+ }
+
+ public BigInteger getConfigPost() {
+ return BigInteger.valueOf(this.configPost.get());
+ }
+
+ public BigInteger getConfigPut() {
+ return BigInteger.valueOf(this.configPut.get());
+ }
+
+ public BigInteger getOperationalGet() {
+ return BigInteger.valueOf(this.operationalGet.get());
+ }
+
+ public BigInteger getRpc() {
+ return BigInteger.valueOf(this.rpc.get());
+ }
+
+ public BigInteger getSuccessGetConfig() {
+ return BigInteger.valueOf(this.successGetConfig.get());
+ }
+
+ public BigInteger getSuccessGetOperational() {
+ return BigInteger.valueOf(this.successGetOperational.get());
+ }
+
+ public BigInteger getSuccessPost() {
+ return BigInteger.valueOf(this.successPost.get());
+ }
+
+ public BigInteger getSuccessPut() {
+ return BigInteger.valueOf(this.successPut.get());
+ }
+
+ public BigInteger getSuccessDelete() {
+ return BigInteger.valueOf(this.successDelete.get());
+ }
+
+ public BigInteger getFailureGetConfig() {
+ return BigInteger.valueOf(this.failureGetConfig.get());
+ }
+
+ public BigInteger getFailureGetOperational() {
+ return BigInteger.valueOf(this.failureGetOperational.get());
+ }
+
+ public BigInteger getFailurePost() {
+ return BigInteger.valueOf(this.failurePost.get());
+ }
+
+ public BigInteger getFailurePut() {
+ return BigInteger.valueOf(this.failurePut.get());
+ }
+
+ public BigInteger getFailureDelete() {
+ return BigInteger.valueOf(this.failureDelete.get());
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java
new file mode 100644
index 0000000..a115254
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Config.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public class Config {
+ private Delete delete;
+
+ private Post post;
+
+ private Get get;
+
+ private Put put;
+
+ public Delete getDelete() {
+ return delete;
+ }
+
+ public void setDelete(Delete delete) {
+ this.delete = delete;
+ }
+
+ public Post getPost() {
+ return post;
+ }
+
+ public void setPost(Post post) {
+ this.post = post;
+ }
+
+ public Get getGet() {
+ return get;
+ }
+
+ public void setGet(Get get) {
+ this.get = get;
+ }
+
+ public Put getPut() {
+ return put;
+ }
+
+ public void setPut(Put put) {
+ this.put = put;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(delete, post, get, put);
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Config that = (Config) obj;
+ if (!java.util.Objects.equals(delete, that.delete)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(post, that.post)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(get, that.get)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(put, that.put);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java
new file mode 100644
index 0000000..16a815b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Delete.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Delete {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Delete that = (Delete) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java
new file mode 100644
index 0000000..45603a8
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Get.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Get {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Get that = (Get) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java
new file mode 100644
index 0000000..5d9989b
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Operational.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public class Operational {
+ private Get get;
+
+ public Get getGet() {
+ return get;
+ }
+
+ public void setGet(Get get) {
+ this.get = get;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(get);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Operational that = (Operational) obj;
+ return java.util.Objects.equals(get, that.get);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java
new file mode 100644
index 0000000..a466c01
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Post.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Post {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Post that = (Post) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java
new file mode 100644
index 0000000..589f02c
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Put.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Put {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Put that = (Put) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java
new file mode 100644
index 0000000..cafcb8f
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/RestConnectorRuntimeMXBean.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+public interface RestConnectorRuntimeMXBean {
+ Operational getOperational();
+
+ Rpcs getRpcs();
+
+ Config getConfig();
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java
new file mode 100644
index 0000000..d849a4d
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/Rpcs.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
+
+import java.math.BigInteger;
+
+public class Rpcs {
+ private BigInteger successfulResponses;
+
+ private BigInteger receivedRequests;
+
+ private BigInteger failedResponses;
+
+ public BigInteger getSuccessfulResponses() {
+ return successfulResponses;
+ }
+
+ public void setSuccessfulResponses(BigInteger successfulResponses) {
+ this.successfulResponses = successfulResponses;
+ }
+
+ public BigInteger getReceivedRequests() {
+ return receivedRequests;
+ }
+
+ public void setReceivedRequests(BigInteger receivedRequests) {
+ this.receivedRequests = receivedRequests;
+ }
+
+ public BigInteger getFailedResponses() {
+ return failedResponses;
+ }
+
+ public void setFailedResponses(BigInteger failedResponses) {
+ this.failedResponses = failedResponses;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(successfulResponses, receivedRequests, failedResponses);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Rpcs that = (Rpcs) obj;
+ if (!java.util.Objects.equals(successfulResponses, that.successfulResponses)) {
+ return false;
+ }
+
+ if (!java.util.Objects.equals(receivedRequests, that.receivedRequests)) {
+ return false;
+ }
+
+ return java.util.Objects.equals(failedResponses, that.failedResponses);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java
new file mode 100644
index 0000000..d67ee37
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/jmx/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2017 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/**
+ * This package contains the statistical JMX classes for the Draft02 restconf implementation. Originally these classes
+ * were generated by the CSS code generator and were moved to this package on conversion to blueprint.
+ */
+package org.opendaylight.netconf.sal.restconf.impl.jmx;
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java
new file mode 100644
index 0000000..e78fddd
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/package-info.java
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.impl; \ No newline at end of file
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/web/WebInitializer.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/web/WebInitializer.java
new file mode 100644
index 0000000..d17c485
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/web/WebInitializer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018 Inocybe Technologies and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.restconf.web;
+
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import org.opendaylight.aaa.filterchain.configuration.CustomFilterAdapterConfiguration;
+import org.opendaylight.aaa.filterchain.filters.CustomFilterAdapter;
+import org.opendaylight.aaa.web.FilterDetails;
+import org.opendaylight.aaa.web.ServletDetails;
+import org.opendaylight.aaa.web.WebContext;
+import org.opendaylight.aaa.web.WebContextBuilder;
+import org.opendaylight.aaa.web.WebContextRegistration;
+import org.opendaylight.aaa.web.WebContextSecurer;
+import org.opendaylight.aaa.web.WebServer;
+import org.opendaylight.aaa.web.servlet.ServletSupport;
+import org.opendaylight.netconf.sal.rest.impl.RestconfApplication;
+
+/**
+ * Initializes the bierman-02 endpoint.
+ *
+ * @author Thomas Pantelis
+ */
+@Singleton
+public class WebInitializer {
+
+ private final WebContextRegistration registration;
+
+ @Inject
+ public WebInitializer(final WebServer webServer, final WebContextSecurer webContextSecurer,
+ final ServletSupport servletSupport, final RestconfApplication webApp,
+ final CustomFilterAdapterConfiguration customFilterAdapterConfig) throws ServletException {
+
+ WebContextBuilder webContextBuilder = WebContext.builder().contextPath("restconf").supportsSessions(false)
+ .addServlet(ServletDetails.builder().servlet(servletSupport.createHttpServletBuilder(webApp).build())
+ .addUrlPattern("/*").build())
+
+ // Allows user to add javax.servlet.Filter(s) in front of REST services
+ .addFilter(FilterDetails.builder().filter(new CustomFilterAdapter(customFilterAdapterConfig))
+ .addUrlPattern("/*").build());
+
+ webContextSecurer.requireAuthentication(webContextBuilder, "/*");
+
+ registration = webServer.registerWebContext(webContextBuilder.build());
+ }
+
+ @PreDestroy
+ public void close() {
+ if (registration != null) {
+ registration.close();
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractCommonSubscriber.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractCommonSubscriber.java
new file mode 100644
index 0000000..76827d4
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractCommonSubscriber.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+import io.netty.channel.Channel;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Features of subscribing part of both notifications.
+ */
+abstract class AbstractCommonSubscriber extends AbstractQueryParams implements BaseListenerInterface {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractCommonSubscriber.class);
+
+ private final Set<Channel> subscribers = ConcurrentHashMap.newKeySet();
+ private final EventBus eventBus;
+
+ @SuppressWarnings("rawtypes")
+ private EventBusChangeRecorder eventBusChangeRecorder;
+ @SuppressWarnings("rawtypes")
+ private ListenerRegistration registration;
+
+ /**
+ * Creating {@link EventBus}.
+ */
+ protected AbstractCommonSubscriber() {
+ this.eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor());
+ }
+
+ @Override
+ public final boolean hasSubscribers() {
+ return !this.subscribers.isEmpty();
+ }
+
+ @Override
+ public final Set<Channel> getSubscribers() {
+ return this.subscribers;
+ }
+
+ @Override
+ public final void close() {
+ if (registration != null) {
+ this.registration.close();
+ this.registration = null;
+ }
+
+ unregister();
+ }
+
+ /**
+ * 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);
+ this.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 subscriber channel
+ */
+ public void removeSubscriber(final Channel subscriber) {
+ LOG.debug("Subscriber {} is removed.", subscriber.remoteAddress());
+ final Event event = new Event(EventType.DEREGISTER);
+ event.setSubscriber(subscriber);
+ this.eventBus.post(event);
+ }
+
+ /**
+ * Sets {@link ListenerRegistration} registration.
+ *
+ * @param registration
+ * DOMDataChangeListener registration
+ */
+ @SuppressWarnings("rawtypes")
+ public void setRegistration(final ListenerRegistration registration) {
+ this.registration = registration;
+ }
+
+ /**
+ * Checks if {@link ListenerRegistration} registration exist.
+ *
+ * @return True if exist, false otherwise.
+ */
+ public boolean isListening() {
+ return this.registration != null;
+ }
+
+ /**
+ * Creating and registering {@link EventBusChangeRecorder} of specific
+ * listener on {@link EventBus}.
+ *
+ * @param listener
+ * specific listener of notifications
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected <T extends BaseListenerInterface> void register(final T listener) {
+ this.eventBusChangeRecorder = new EventBusChangeRecorder(listener);
+ this.eventBus.register(this.eventBusChangeRecorder);
+ }
+
+ /**
+ * Post event to event bus.
+ *
+ * @param event
+ * data of incoming notifications
+ */
+ protected void post(final Event event) {
+ this.eventBus.post(event);
+ }
+
+ /**
+ * Removes all subscribers and unregisters event bus change recorder form
+ * event bus.
+ */
+ protected void unregister() {
+ this.subscribers.clear();
+ this.eventBus.unregister(this.eventBusChangeRecorder);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractNotificationsData.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractNotificationsData.java
new file mode 100644
index 0000000..7e7bc1a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractNotificationsData.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+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.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+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.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Abstract class for processing and preparing data.
+ *
+ */
+abstract class AbstractNotificationsData {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractNotificationsData.class);
+ private static final TransformerFactory TF = TransformerFactory.newInstance();
+ private static final XMLOutputFactory OF = XMLOutputFactory.newFactory();
+
+ /**
+ * Formats data specified by RFC3339.
+ *
+ * @param now time stamp
+ * @return Data specified by RFC3339.
+ */
+ protected static String toRFC3339(final Instant now) {
+ return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(now, ZoneId.systemDefault()));
+ }
+
+ /**
+ * Creates {@link Document} document.
+ *
+ * @return {@link Document} document.
+ */
+ protected static Document createDocument() {
+ return UntrustedXML.newDocumentBuilder().newDocument();
+ }
+
+ /**
+ * Write normalized node to {@link DOMResult}.
+ *
+ * @param normalized
+ * data
+ * @param inference
+ * SchemaInferenceStack state for the data
+ * @return {@link DOMResult}
+ */
+ protected DOMResult writeNormalizedNode(final NormalizedNode normalized, final Inference inference)
+ throws IOException, XMLStreamException {
+ final Document doc = UntrustedXML.newDocumentBuilder().newDocument();
+ final DOMResult result = new DOMResult(doc);
+ NormalizedNodeWriter normalizedNodeWriter = null;
+ NormalizedNodeStreamWriter normalizedNodeStreamWriter = null;
+ XMLStreamWriter writer = null;
+
+ try {
+ writer = OF.createXMLStreamWriter(result);
+ normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, inference);
+ 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;
+ }
+
+ /**
+ * Generating base element of every notification.
+ *
+ * @param doc
+ * base {@link Document}
+ * @return element of {@link Document}
+ */
+ protected Element basePartDoc(final Document doc) {
+ 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(Instant.now()));
+ notificationElement.appendChild(eventTimeElement);
+
+ return notificationElement;
+ }
+
+ /**
+ * Generating of {@link Document} transforming to string.
+ *
+ * @param doc
+ * {@link Document} with data
+ * @return - string from {@link Document}
+ */
+ protected String transformDoc(final Document doc) {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ try {
+ final Transformer transformer = TF.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(out));
+ } catch (final TransformerException e) {
+ // FIXME: this should raise an exception
+ final String msg = "Error during transformation of Document into String";
+ LOG.error(msg, e);
+ return msg;
+ }
+
+ return new String(out.toByteArray(), StandardCharsets.UTF_8);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractQueryParams.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractQueryParams.java
new file mode 100644
index 0000000..4697646
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/AbstractQueryParams.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.StringReader;
+import java.time.Instant;
+import java.util.Optional;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+/**
+ * Features of query parameters part of both notifications.
+ *
+ */
+abstract class AbstractQueryParams extends AbstractNotificationsData {
+ // FIXME: BUG-7956: switch to using UntrustedXML
+ private static final DocumentBuilderFactory DBF;
+
+ static {
+ final DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+ f.setCoalescing(true);
+ f.setExpandEntityReferences(false);
+ f.setIgnoringElementContentWhitespace(true);
+ f.setIgnoringComments(true);
+ f.setXIncludeAware(false);
+ try {
+ f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ f.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ } catch (final ParserConfigurationException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ DBF = f;
+ }
+
+ // FIXME: these should be final
+ private Instant start = null;
+ private Instant stop = null;
+ private String filter = null;
+ private boolean leafNodesOnly = false;
+ private boolean skipNotificationData = false;
+
+ @VisibleForTesting
+ public final Instant getStart() {
+ return start;
+ }
+
+ /**
+ * Set query parameters for listener.
+ *
+ * @param start
+ * start-time of getting notification
+ * @param stop
+ * stop-time of getting notification
+ * @param filter
+ * indicate which subset of all possible events are of interest
+ * @param leafNodesOnly
+ * if true, notifications will contain changes to leaf nodes only
+ * @param skipNotificationData
+ * if true, notification will not contain changed data
+ */
+ @SuppressWarnings("checkstyle:hiddenField")
+ public void setQueryParams(final Instant start, final Optional<Instant> stop, final Optional<String> filter,
+ final boolean leafNodesOnly, final boolean skipNotificationData) {
+ this.start = requireNonNull(start);
+ this.stop = stop.orElse(null);
+ this.filter = filter.orElse(null);
+ this.leafNodesOnly = leafNodesOnly;
+ this.skipNotificationData = skipNotificationData;
+ }
+
+ /**
+ * Check whether this query should only notify about leaf node changes.
+ *
+ * @return true if this query should only notify about leaf node changes
+ */
+ public boolean getLeafNodesOnly() {
+ return leafNodesOnly;
+ }
+
+ /**
+ * Check whether this query should notify changes without data.
+ *
+ * @return true if this query should notify about changes with data
+ */
+ public boolean isSkipNotificationData() {
+ return skipNotificationData;
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ <T extends BaseListenerInterface> boolean checkStartStop(final Instant now, final T listener) {
+ if (this.stop != null) {
+ if (this.start.compareTo(now) < 0 && this.stop.compareTo(now) > 0) {
+ return true;
+ }
+ if (this.stop.compareTo(now) < 0) {
+ try {
+ listener.close();
+ } catch (final Exception e) {
+ throw new RestconfDocumentedException("Problem with unregister listener." + e);
+ }
+ }
+ } else if (this.start != null) {
+ if (this.start.compareTo(now) < 0) {
+ this.start = null;
+ return true;
+ }
+ } else {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if is filter used and then prepare and post data do client.
+ *
+ * @param xml data of notification
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ boolean checkFilter(final String xml) {
+ if (this.filter == null) {
+ return true;
+ }
+
+ try {
+ return parseFilterParam(xml);
+ } catch (final Exception e) {
+ throw new RestconfDocumentedException("Problem while parsing filter.", e);
+ }
+ }
+
+ /**
+ * Parse and evaluate filter value by xml.
+ *
+ * @return true or false - depends on filter expression and data of
+ * notifiaction
+ * @throws Exception if operation fails
+ */
+ private boolean parseFilterParam(final String xml) throws Exception {
+ final Document docOfXml = DBF.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
+ final XPath xPath = XPathFactory.newInstance().newXPath();
+ // FIXME: BUG-7956: xPath.setNamespaceContext(nsContext);
+ return (boolean) xPath.compile(this.filter).evaluate(docOfXml, XPathConstants.BOOLEAN);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/BaseListenerInterface.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/BaseListenerInterface.java
new file mode 100644
index 0000000..4804e16
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/BaseListenerInterface.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import io.netty.channel.Channel;
+import java.util.Set;
+
+/**
+ * Base interface for both listeners({@link ListenerAdapter},
+ * {@link NotificationListenerAdapter}).
+ */
+interface BaseListenerInterface extends AutoCloseable {
+
+ /**
+ * Return all subscribers of listener.
+ *
+ * @return set of subscribers
+ */
+ Set<Channel> getSubscribers();
+
+ /**
+ * Checks if exists at least one {@link Channel} subscriber.
+ *
+ * @return True if exist at least one {@link Channel} subscriber, false
+ * otherwise.
+ */
+ boolean hasSubscribers();
+
+ /**
+ * Get name of stream.
+ *
+ * @return stream name
+ */
+ String getStreamName();
+
+ /**
+ * Get output type.
+ *
+ * @return outputType
+ */
+ String getOutputType();
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Event.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Event.java
new file mode 100644
index 0000000..486d807
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Event.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import io.netty.channel.Channel;
+
+/**
+ * Represents event of specific {@link EventType} type, holds data and
+ * {@link Channel} subscriber.
+ */
+class Event {
+ private final EventType type;
+ private Channel subscriber;
+ private String data;
+
+ /**
+ * Creates new event specified by {@link EventType} type.
+ *
+ * @param type
+ * EventType
+ */
+ Event(final EventType type) {
+ this.type = type;
+ }
+
+ /**
+ * Gets the {@link Channel} subscriber.
+ *
+ * @return Channel
+ */
+ public Channel getSubscriber() {
+ return this.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 this.data;
+ }
+
+ /**
+ * Sets event data.
+ *
+ * @param data
+ * String.
+ */
+ public void setData(final String data) {
+ this.data = data;
+ }
+
+ /**
+ * Gets event type.
+ *
+ * @return The type of the event.
+ */
+ public EventType getType() {
+ return this.type;
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventBusChangeRecorder.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventBusChangeRecorder.java
new file mode 100644
index 0000000..11e5656
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventBusChangeRecorder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import com.google.common.eventbus.Subscribe;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class EventBusChangeRecorder<T extends BaseListenerInterface> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EventBusChangeRecorder.class);
+ private final T listener;
+
+ /**
+ * Event bus change recorder of specific listener of notifications.
+ *
+ * @param listener
+ * specific listener
+ */
+ EventBusChangeRecorder(final T listener) {
+ this.listener = listener;
+ }
+
+ @Subscribe
+ public void recordCustomerChange(final Event event) {
+ if (event.getType() == EventType.REGISTER) {
+ final Channel subscriber = event.getSubscriber();
+ if (!this.listener.getSubscribers().contains(subscriber)) {
+ this.listener.getSubscribers().add(subscriber);
+ }
+ } else if (event.getType() == EventType.DEREGISTER) {
+ this.listener.getSubscribers().remove(event.getSubscriber());
+ Notificator.removeListenerIfNoSubscriberExists(this.listener);
+ } else if (event.getType() == EventType.NOTIFY) {
+ for (final Channel subscriber : this.listener.getSubscribers()) {
+ 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());
+ this.listener.getSubscribers().remove(subscriber);
+ }
+ }
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventType.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventType.java
new file mode 100644
index 0000000..ba7c2a3
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/EventType.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+/**
+ * Type of the event.
+ */
+enum EventType {
+ REGISTER, DEREGISTER, NOTIFY
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
new file mode 100644
index 0000000..fc85862
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 2014, 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Optional;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMResult;
+import org.json.XML;
+import org.opendaylight.mdsal.dom.api.ClusteredDOMDataTreeChangeListener;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+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;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+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.tree.api.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+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.
+ */
+public class ListenerAdapter extends AbstractCommonSubscriber implements ClusteredDOMDataTreeChangeListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class);
+ private static final String DATA_CHANGE_EVENT = "data-change-event";
+ private static final String PATH = "path";
+ private static final String OPERATION = "operation";
+
+ private final ControllerContext controllerContext;
+ private final YangInstanceIdentifier path;
+ private final String streamName;
+ private final NotificationOutputType outputType;
+
+ /**
+ * 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)
+ */
+ @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "non-final for testing")
+ ListenerAdapter(final YangInstanceIdentifier path, final String streamName,
+ final NotificationOutputType outputType, final ControllerContext controllerContext) {
+ this.outputType = requireNonNull(outputType);
+ this.path = requireNonNull(path);
+ checkArgument(streamName != null && !streamName.isEmpty());
+ this.streamName = streamName;
+ this.controllerContext = controllerContext;
+ register(this);
+ }
+
+ @Override
+ public void onInitialData() {
+ // No-op
+ }
+
+ @Override
+ public void onDataTreeChanged(final List<DataTreeCandidate> dataTreeCandidates) {
+ final Instant now = Instant.now();
+ if (!checkStartStop(now, this)) {
+ return;
+ }
+
+ final String xml = prepareXml(dataTreeCandidates);
+ if (checkFilter(xml)) {
+ prepareAndPostData(xml);
+ }
+ }
+
+ /**
+ * Gets the name of the stream.
+ *
+ * @return The name of the stream.
+ */
+ @Override
+ public String getStreamName() {
+ return streamName;
+ }
+
+ @Override
+ public String getOutputType() {
+ return outputType.getName();
+ }
+
+ /**
+ * Get path pointed to data in data store.
+ *
+ * @return Path pointed to data in data store.
+ */
+ public YangInstanceIdentifier getPath() {
+ return path;
+ }
+
+ /**
+ * Prepare data of notification and data to client.
+ *
+ * @param xml data
+ */
+ private void prepareAndPostData(final String xml) {
+ final Event event = new Event(EventType.NOTIFY);
+ if (outputType.equals(NotificationOutputType.JSON)) {
+ event.setData(XML.toJSONObject(xml).toString());
+ } else {
+ event.setData(xml);
+ }
+ post(event);
+ }
+
+ /**
+ * Tracks events of data change by customer.
+ */
+
+ /**
+ * Prepare data in printable form and transform it to String.
+ *
+ * @return Data in printable form.
+ */
+ private String prepareXml(final Collection<DataTreeCandidate> candidates) {
+ final EffectiveModelContext schemaContext = controllerContext.getGlobalSchema();
+ final DataSchemaContextTree dataContextTree = DataSchemaContextTree.from(schemaContext);
+ final Document doc = createDocument();
+ 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, candidates,
+ schemaContext, dataContextTree);
+ notificationElement.appendChild(dataChangedNotificationEventElement);
+ return transformDoc(doc);
+ }
+
+ /**
+ * Adds values to data changed notification event element.
+ *
+ * @param doc
+ * {@link Document}
+ * @param dataChangedNotificationEventElement
+ * {@link Element}
+ * @param dataTreeCandidates
+ * {@link DataTreeCandidate}
+ */
+ private void addValuesToDataChangedNotificationEventElement(final Document doc,
+ final Element dataChangedNotificationEventElement,
+ final Collection<DataTreeCandidate> dataTreeCandidates,
+ final EffectiveModelContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
+
+ for (DataTreeCandidate dataTreeCandidate : dataTreeCandidates) {
+ DataTreeCandidateNode candidateNode = dataTreeCandidate.getRootNode();
+ if (candidateNode == null) {
+ continue;
+ }
+ YangInstanceIdentifier yiid = dataTreeCandidate.getRootPath();
+
+ boolean isSkipNotificationData = this.isSkipNotificationData();
+ if (isSkipNotificationData) {
+ createCreatedChangedDataChangeEventElementWithoutData(doc,
+ dataChangedNotificationEventElement, dataTreeCandidate.getRootNode());
+ } else {
+ addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, candidateNode,
+ yiid.getParent(), schemaContext, dataSchemaContextTree);
+ }
+ }
+ }
+
+ private void addNodeToDataChangeNotificationEventElement(final Document doc,
+ final Element dataChangedNotificationEventElement, final DataTreeCandidateNode candidateNode,
+ final YangInstanceIdentifier parentYiid, final EffectiveModelContext schemaContext,
+ final DataSchemaContextTree dataSchemaContextTree) {
+
+ Optional<NormalizedNode> optionalNormalizedNode = Optional.empty();
+ switch (candidateNode.getModificationType()) {
+ case APPEARED:
+ case SUBTREE_MODIFIED:
+ case WRITE:
+ optionalNormalizedNode = candidateNode.getDataAfter();
+ break;
+ case DELETE:
+ case DISAPPEARED:
+ optionalNormalizedNode = candidateNode.getDataBefore();
+ break;
+ case UNMODIFIED:
+ default:
+ break;
+ }
+
+ if (optionalNormalizedNode.isEmpty()) {
+ LOG.error("No node present in notification for {}", candidateNode);
+ return;
+ }
+
+ NormalizedNode normalizedNode = optionalNormalizedNode.get();
+ YangInstanceIdentifier yiid = YangInstanceIdentifier.builder(parentYiid)
+ .append(normalizedNode.getIdentifier()).build();
+
+ boolean isNodeMixin = controllerContext.isNodeMixin(yiid);
+ boolean isSkippedNonLeaf = getLeafNodesOnly() && !(normalizedNode instanceof LeafNode);
+ if (!isNodeMixin && !isSkippedNonLeaf) {
+ Node node = null;
+ switch (candidateNode.getModificationType()) {
+ case APPEARED:
+ case SUBTREE_MODIFIED:
+ case WRITE:
+ Operation op = candidateNode.getDataBefore().isPresent() ? Operation.UPDATED : Operation.CREATED;
+ node = createCreatedChangedDataChangeEventElement(doc, yiid, normalizedNode, op,
+ schemaContext, dataSchemaContextTree);
+ break;
+ case DELETE:
+ case DISAPPEARED:
+ node = createDataChangeEventElement(doc, yiid, Operation.DELETED);
+ break;
+ case UNMODIFIED:
+ default:
+ break;
+ }
+ if (node != null) {
+ dataChangedNotificationEventElement.appendChild(node);
+ }
+ }
+
+ for (DataTreeCandidateNode childNode : candidateNode.getChildNodes()) {
+ addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, childNode,
+ yiid, schemaContext, dataSchemaContextTree);
+ }
+ }
+
+ /**
+ * Creates changed event element from data.
+ *
+ * @param doc
+ * {@link Document}
+ * @param dataPath
+ * Path to data in data store.
+ * @param operation
+ * {@link Operation}
+ * @return {@link Node} node represented by changed event element.
+ */
+ private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier dataPath,
+ final Operation operation) {
+ final Element dataChangeEventElement = doc.createElement(DATA_CHANGE_EVENT);
+ final Element pathElement = doc.createElement(PATH);
+ addPathAsValueToElement(dataPath, pathElement);
+ dataChangeEventElement.appendChild(pathElement);
+
+ final Element operationElement = doc.createElement(OPERATION);
+ operationElement.setTextContent(operation.value);
+ dataChangeEventElement.appendChild(operationElement);
+
+ return dataChangeEventElement;
+ }
+
+ /**
+ * Creates data change notification element without data element.
+ *
+ * @param doc
+ * {@link Document}
+ * @param dataChangedNotificationEventElement
+ * {@link Element}
+ * @param candidateNode
+ * {@link DataTreeCandidateNode}
+ */
+ private void createCreatedChangedDataChangeEventElementWithoutData(final Document doc,
+ final Element dataChangedNotificationEventElement, final DataTreeCandidateNode candidateNode) {
+ final Operation operation;
+ switch (candidateNode.getModificationType()) {
+ case APPEARED:
+ case SUBTREE_MODIFIED:
+ case WRITE:
+ operation = candidateNode.getDataBefore().isPresent() ? Operation.UPDATED : Operation.CREATED;
+ break;
+ case DELETE:
+ case DISAPPEARED:
+ operation = Operation.DELETED;
+ break;
+ case UNMODIFIED:
+ default:
+ return;
+ }
+ Node dataChangeEventElement = createDataChangeEventElement(doc, getPath(), operation);
+ dataChangedNotificationEventElement.appendChild(dataChangeEventElement);
+
+ }
+
+ private Node createCreatedChangedDataChangeEventElement(final Document doc,
+ final YangInstanceIdentifier eventPath, final NormalizedNode normalized, final Operation operation,
+ final EffectiveModelContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
+ final Element dataChangeEventElement = doc.createElement(DATA_CHANGE_EVENT);
+ final Element pathElement = doc.createElement(PATH);
+ addPathAsValueToElement(eventPath, pathElement);
+ dataChangeEventElement.appendChild(pathElement);
+
+ final Element operationElement = doc.createElement(OPERATION);
+ operationElement.setTextContent(operation.value);
+ dataChangeEventElement.appendChild(operationElement);
+
+ final SchemaInferenceStack stack = dataSchemaContextTree.enterPath(eventPath).orElseThrow().stack();
+ if (!(normalized instanceof MapEntryNode) && !(normalized instanceof UnkeyedListEntryNode)
+ && !stack.isEmpty()) {
+ stack.exit();
+ }
+
+ final var inference = stack.toInference();
+
+ try {
+ final DOMResult domResult = writeNormalizedNode(normalized, inference);
+ final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
+ final Element dataElement = doc.createElement("data");
+ dataElement.appendChild(result);
+ dataChangeEventElement.appendChild(dataElement);
+ } catch (final IOException e) {
+ LOG.error("Error in writer ", e);
+ } catch (final XMLStreamException e) {
+ LOG.error("Error processing stream", e);
+ }
+
+ return dataChangeEventElement;
+ }
+
+ /**
+ * Adds path as value to element.
+ *
+ * @param dataPath
+ * Path to data in data store.
+ * @param element
+ * {@link Element}
+ */
+ @SuppressWarnings("rawtypes")
+ private void addPathAsValueToElement(final YangInstanceIdentifier dataPath, final Element element) {
+ final YangInstanceIdentifier normalizedPath = controllerContext.toXpathRepresentation(dataPath);
+ final StringBuilder textContent = new StringBuilder();
+
+ for (final PathArgument pathArgument : normalizedPath.getPathArguments()) {
+ if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+ continue;
+ }
+ textContent.append("/");
+ writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType());
+ if (pathArgument instanceof NodeIdentifierWithPredicates) {
+ for (final Entry<QName, Object> entry : ((NodeIdentifierWithPredicates) pathArgument).entrySet()) {
+ final QName keyValue = entry.getKey();
+ final String predicateValue = String.valueOf(entry.getValue());
+ textContent.append("[");
+ writeIdentifierWithNamespacePrefix(element, textContent, keyValue);
+ textContent.append("='");
+ textContent.append(predicateValue);
+ textContent.append("'");
+ textContent.append("]");
+ }
+ } else if (pathArgument instanceof NodeWithValue) {
+ textContent.append("[.='");
+ textContent.append(((NodeWithValue) pathArgument).getValue());
+ textContent.append("'");
+ textContent.append("]");
+ }
+ }
+ element.setTextContent(textContent.toString());
+ }
+
+ /**
+ * Writes identifier that consists of prefix and QName.
+ *
+ * @param element
+ * {@link Element}
+ * @param textContent
+ * StringBuilder
+ * @param qualifiedName
+ * QName
+ */
+ private void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent,
+ final QName qualifiedName) {
+ final Module module = controllerContext.getGlobalSchema().findModule(qualifiedName.getModule())
+ .get();
+
+ textContent.append(module.getName());
+ textContent.append(":");
+ textContent.append(qualifiedName.getLocalName());
+ }
+
+ /**
+ * Consists of three types {@link Operation#CREATED},
+ * {@link Operation#UPDATED} and {@link Operation#DELETED}.
+ */
+ private enum Operation {
+ CREATED("created"), UPDATED("updated"), DELETED("deleted");
+
+ private final String value;
+
+ Operation(final String value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java
new file mode 100644
index 0000000..3061285
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.time.Instant;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMResult;
+import org.opendaylight.mdsal.dom.api.DOMNotification;
+import org.opendaylight.mdsal.dom.api.DOMNotificationListener;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+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.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * {@link NotificationListenerAdapter} is responsible to track events on notifications.
+ */
+public final class NotificationListenerAdapter extends AbstractCommonSubscriber implements DOMNotificationListener {
+ private static final Logger LOG = LoggerFactory.getLogger(NotificationListenerAdapter.class);
+
+ private final ControllerContext controllerContext;
+ private final String streamName;
+ private final Absolute path;
+ private final String outputType;
+
+ /**
+ * Set path of listener and stream name, register event bus.
+ *
+ * @param path
+ * path of notification
+ * @param streamName
+ * stream name of listener
+ * @param outputType
+ * type of output on notification (JSON, XML)
+ */
+ NotificationListenerAdapter(final Absolute path, final String streamName, final String outputType,
+ final ControllerContext controllerContext) {
+ register(this);
+ this.outputType = requireNonNull(outputType);
+ this.path = requireNonNull(path);
+ checkArgument(streamName != null && !streamName.isEmpty());
+ this.streamName = streamName;
+ this.controllerContext = controllerContext;
+ }
+
+ /**
+ * Get outputType of listener.
+ *
+ * @return the outputType
+ */
+ @Override
+ public String getOutputType() {
+ return outputType;
+ }
+
+ @Override
+ public void onNotification(final DOMNotification notification) {
+ final Instant now = Instant.now();
+ if (!checkStartStop(now, this)) {
+ return;
+ }
+
+ final EffectiveModelContext schemaContext = controllerContext.getGlobalSchema();
+ final String xml = prepareXml(schemaContext, notification);
+ if (checkFilter(xml)) {
+ prepareAndPostData(outputType.equals("JSON") ? prepareJson(schemaContext, notification) : xml);
+ }
+ }
+
+ /**
+ * Get stream name of this listener.
+ *
+ * @return {@link String}
+ */
+ @Override
+ public String getStreamName() {
+ return streamName;
+ }
+
+ /**
+ * Get schema path of notification.
+ *
+ * @return {@link Absolute} SchemaNodeIdentifier
+ */
+ public Absolute getSchemaPath() {
+ return path;
+ }
+
+ /**
+ * Prepare data of notification and data to client.
+ *
+ * @param data data
+ */
+ private void prepareAndPostData(final String data) {
+ final Event event = new Event(EventType.NOTIFY);
+ event.setData(data);
+ post(event);
+ }
+
+ /**
+ * Prepare json from notification data.
+ *
+ * @return json as {@link String}
+ */
+ @VisibleForTesting
+ String prepareJson(final EffectiveModelContext schemaContext, final DOMNotification notification) {
+ final JsonObject json = new JsonObject();
+ json.add("ietf-restconf:notification", JsonParser.parseString(writeBodyToString(schemaContext, notification)));
+ json.addProperty("event-time", ListenerAdapter.toRFC3339(Instant.now()));
+ return json.toString();
+ }
+
+ private static String writeBodyToString(final EffectiveModelContext schemaContext,
+ final DOMNotification notification) {
+ final Writer writer = new StringWriter();
+ final NormalizedNodeStreamWriter jsonStream = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+ JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(schemaContext),
+ notification.getType(), null, JsonWriterFactory.createJsonWriter(writer));
+ final NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream);
+ try {
+ nodeWriter.write(notification.getBody());
+ nodeWriter.close();
+ } catch (final IOException e) {
+ throw new RestconfDocumentedException("Problem while writing body of notification to JSON. ", e);
+ }
+ return writer.toString();
+ }
+
+ private String prepareXml(final EffectiveModelContext schemaContext, final DOMNotification notification) {
+ final Document doc = createDocument();
+ final Element notificationElement = basePartDoc(doc);
+
+ final Element notificationEventElement = doc.createElementNS(
+ "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "create-notification-stream");
+ addValuesToNotificationEventElement(doc, notificationEventElement, schemaContext, notification);
+ notificationElement.appendChild(notificationEventElement);
+
+ return transformDoc(doc);
+ }
+
+ private void addValuesToNotificationEventElement(final Document doc, final Element element,
+ final EffectiveModelContext schemaContext, final DOMNotification notification) {
+ try {
+ final DOMResult domResult = writeNormalizedNode(notification.getBody(),
+ SchemaInferenceStack.of(schemaContext, path).toInference());
+ final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
+ final Element dataElement = doc.createElement("notification");
+ dataElement.appendChild(result);
+ element.appendChild(dataElement);
+ } catch (final IOException e) {
+ LOG.error("Error in writer ", e);
+ } catch (final XMLStreamException e) {
+ LOG.error("Error processing stream", e);
+ }
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java
new file mode 100644
index 0000000..5bfa79f
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.listeners;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link Notificator} is responsible to create, remove and find
+ * {@link ListenerAdapter} listener.
+ */
+public final class Notificator {
+
+ private static Map<String, ListenerAdapter> dataChangeListener = new ConcurrentHashMap<>();
+ private static Map<String, List<NotificationListenerAdapter>> notificationListenersByStreamName =
+ new ConcurrentHashMap<>();
+
+ private static final Logger LOG = LoggerFactory.getLogger(Notificator.class);
+ private static final Lock LOCK = new ReentrantLock();
+
+ private Notificator() {
+ }
+
+ /**
+ * Returns list of all stream names.
+ */
+ public static Set<String> getStreamNames() {
+ return dataChangeListener.keySet();
+ }
+
+ /**
+ * Gets {@link ListenerAdapter} specified by stream name.
+ *
+ * @param streamName
+ * The name of the stream.
+ * @return {@link ListenerAdapter} specified by stream name.
+ */
+ public static ListenerAdapter getListenerFor(final String streamName) {
+ return dataChangeListener.get(streamName);
+ }
+
+ /**
+ * Checks if the listener specified by {@link YangInstanceIdentifier} path exist.
+ *
+ * @param streamName name of the stream
+ * @return True if the listener exist, false otherwise.
+ */
+ public static boolean existListenerFor(final String streamName) {
+ return dataChangeListener.containsKey(streamName);
+ }
+
+ /**
+ * Creates new {@link ListenerAdapter} listener from
+ * {@link YangInstanceIdentifier} path and stream name.
+ *
+ * @param path
+ * Path to data in data repository.
+ * @param streamName
+ * The name of the stream.
+ * @param outputType
+ * Spcific type of output for notifications - XML or JSON
+ * @return New {@link ListenerAdapter} listener from
+ * {@link YangInstanceIdentifier} path and stream name.
+ */
+ public static ListenerAdapter createListener(final YangInstanceIdentifier path, final String streamName,
+ final NotificationOutputType outputType, final ControllerContext controllerContext) {
+ final ListenerAdapter listener = new ListenerAdapter(path, streamName, outputType, controllerContext);
+ try {
+ LOCK.lock();
+ dataChangeListener.put(streamName, listener);
+ } finally {
+ LOCK.unlock();
+ }
+ return listener;
+ }
+
+ /**
+ * Looks for listener determined by {@link YangInstanceIdentifier} path and removes it.
+ * Creates String representation of stream name from URI. Removes slash from URI in start and end position.
+ *
+ * @param uri
+ * URI for creation stream name.
+ * @return String representation of stream name.
+ */
+ public static String createStreamNameFromUri(final String uri) {
+ if (uri == null) {
+ return null;
+ }
+ String result = uri;
+ if (result.startsWith("/")) {
+ result = result.substring(1);
+ }
+ if (result.endsWith("/")) {
+ result = result.substring(0, result.length() - 1);
+ }
+ return result;
+ }
+
+ /**
+ * Removes all listeners.
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public static void removeAllListeners() {
+ for (final ListenerAdapter listener : dataChangeListener.values()) {
+ try {
+ listener.close();
+ } catch (final Exception e) {
+ LOG.error("Failed to close listener", e);
+ }
+ }
+ try {
+ LOCK.lock();
+ dataChangeListener = new ConcurrentHashMap<>();
+ } finally {
+ LOCK.unlock();
+ }
+ }
+
+ /**
+ * Delete {@link ListenerAdapter} listener specified in parameter.
+ *
+ * @param <T>
+ *
+ * @param listener
+ * ListenerAdapter
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private static <T extends BaseListenerInterface> void deleteListener(final T listener) {
+ if (listener != null) {
+ try {
+ listener.close();
+ } catch (final Exception e) {
+ LOG.error("Failed to close listener", e);
+ }
+ try {
+ LOCK.lock();
+ dataChangeListener.remove(listener.getStreamName());
+ } finally {
+ LOCK.unlock();
+ }
+ }
+ }
+
+ /**
+ * Check if the listener specified by qnames of request exist.
+ *
+ * @param streamName
+ * name of stream
+ * @return True if the listener exist, false otherwise.
+ */
+ public static boolean existNotificationListenerFor(final String streamName) {
+ return notificationListenersByStreamName.containsKey(streamName);
+ }
+
+ /**
+ * Prepare listener for notification ({@link NotificationDefinition}).
+ *
+ * @param paths
+ * paths of notifications
+ * @param streamName
+ * name of stream (generated by paths)
+ * @param outputType
+ * type of output for onNotification - XML or JSON
+ * @return List of {@link NotificationListenerAdapter} by paths
+ */
+ public static List<NotificationListenerAdapter> createNotificationListener(final List<Absolute> paths,
+ final String streamName, final String outputType, final ControllerContext controllerContext) {
+ final List<NotificationListenerAdapter> listListeners = new ArrayList<>();
+ for (final Absolute path : paths) {
+ final NotificationListenerAdapter listener =
+ new NotificationListenerAdapter(path, streamName, outputType, controllerContext);
+ listListeners.add(listener);
+ }
+ try {
+ LOCK.lock();
+ notificationListenersByStreamName.put(streamName, listListeners);
+ } finally {
+ LOCK.unlock();
+ }
+ return listListeners;
+ }
+
+ public static <T extends BaseListenerInterface> void removeListenerIfNoSubscriberExists(final T listener) {
+ if (!listener.hasSubscribers()) {
+ if (listener instanceof NotificationListenerAdapter) {
+ deleteNotificationListener(listener);
+ } else {
+ deleteListener(listener);
+ }
+ }
+ }
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private static <T extends BaseListenerInterface> void deleteNotificationListener(final T listener) {
+ if (listener != null) {
+ try {
+ listener.close();
+ } catch (final Exception e) {
+ LOG.error("Failed to close listener", e);
+ }
+ try {
+ LOCK.lock();
+ notificationListenersByStreamName.remove(listener.getStreamName());
+ } finally {
+ LOCK.unlock();
+ }
+ }
+ }
+
+ public static List<NotificationListenerAdapter> getNotificationListenerFor(final String streamName) {
+ return notificationListenersByStreamName.get(streamName);
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServer.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServer.java
new file mode 100644
index 0000000..a295c54
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.websockets;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link WebSocketServer} is the singleton responsible for starting and stopping the
+ * web socket server.
+ */
+public final class WebSocketServer implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class);
+
+ private static final String DEFAULT_ADDRESS = "0.0.0.0";
+
+ private static WebSocketServer instance = null;
+
+ private final String address;
+ private final int port;
+
+ private EventLoopGroup bossGroup;
+ private EventLoopGroup workerGroup;
+
+
+ private WebSocketServer(final String address, final int port) {
+ this.address = address;
+ this.port = port;
+ }
+
+ /**
+ * Create singleton instance of {@link WebSocketServer}.
+ *
+ * @param port TCP port used for this server
+ * @return instance of {@link WebSocketServer}
+ */
+ private static WebSocketServer createInstance(final int port) {
+ instance = createInstance(DEFAULT_ADDRESS, port);
+ return instance;
+ }
+
+ public static WebSocketServer createInstance(final String address, final int port) {
+ checkState(instance == null, "createInstance() has already been called");
+ checkArgument(port >= 1024, "Privileged port (below 1024) is not allowed");
+
+ instance = new WebSocketServer(requireNonNull(address, "Address cannot be null."), port);
+ LOG.info("Created WebSocketServer on {}:{}", address, port);
+ return instance;
+ }
+
+ /**
+ * Get the websocket of TCP port.
+ *
+ * @return websocket TCP port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Get instance of {@link WebSocketServer} created by {@link #createInstance(int)}.
+ *
+ * @return instance of {@link WebSocketServer}
+ */
+ public static WebSocketServer getInstance() {
+ return requireNonNull(instance, "createInstance() must be called prior to getInstance()");
+ }
+
+ /**
+ * Get instance of {@link WebSocketServer} created by {@link #createInstance(int)}.
+ * If an instance doesnt exist create one with the provided fallback port.
+ *
+ * @return instance of {@link WebSocketServer}
+ */
+ public static WebSocketServer getInstance(final int fallbackPort) {
+ if (instance != null) {
+ return instance;
+ }
+
+ LOG.warn("No instance for WebSocketServer found, creating one with a fallback port: {}", fallbackPort);
+ return createInstance(fallbackPort);
+ }
+
+ /**
+ * Destroy the existing instance.
+ */
+ public static void destroyInstance() {
+ checkState(instance != null, "createInstance() must be called prior to destroyInstance()");
+
+ instance.stop();
+ instance = null;
+ LOG.info("Destroyed WebSocketServer.");
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public void run() {
+ bossGroup = new NioEventLoopGroup();
+ workerGroup = new NioEventLoopGroup();
+ try {
+ final ServerBootstrap serverBootstrap = new ServerBootstrap();
+ serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
+ .childHandler(new WebSocketServerInitializer());
+
+ final Channel channel = serverBootstrap.bind(address, port).sync().channel();
+ LOG.info("Web socket server started at address {}, port {}.", address, port);
+
+ channel.closeFuture().sync();
+ } catch (final InterruptedException e) {
+ LOG.error("Web socket server encountered an error during startup attempt on port {}", port, e);
+ } catch (Throwable throwable) {
+ // sync() re-throws exceptions declared as Throwable, so the compiler doesn't see them
+ LOG.error("Error while binding to address {}, port {}", address, port, throwable);
+ throw throwable;
+ } finally {
+ stop();
+ }
+ }
+
+ /**
+ * Stops the web socket server and removes all listeners.
+ */
+ private void stop() {
+ LOG.info("Stopping the web socket server instance on port {}", port);
+ Notificator.removeAllListeners();
+ if (bossGroup != null) {
+ bossGroup.shutdownGracefully();
+ bossGroup = null;
+ }
+ if (workerGroup != null) {
+ workerGroup.shutdownGracefully();
+ workerGroup = null;
+ }
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerHandler.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerHandler.java
new file mode 100644
index 0000000..ed90e3f
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerHandler.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.streams.websockets;
+
+import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
+import static io.netty.handler.codec.http.HttpMethod.GET;
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
+import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
+import static io.netty.handler.codec.http.HttpResponseStatus.OK;
+import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;
+import static io.netty.handler.codec.http.HttpUtil.setContentLength;
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
+import io.netty.util.CharsetUtil;
+import java.util.List;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link WebSocketServerHandler} is implementation of {@link SimpleChannelInboundHandler} which allow handle
+ * {@link FullHttpRequest} and {@link WebSocketFrame} messages.
+ */
+public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
+ private static final Logger LOG = LoggerFactory.getLogger(WebSocketServerHandler.class);
+
+ private WebSocketServerHandshaker handshaker;
+
+ @Override
+ protected void channelRead0(final ChannelHandlerContext ctx, final Object msg) {
+ if (msg instanceof FullHttpRequest) {
+ handleHttpRequest(ctx, (FullHttpRequest) msg);
+ } else if (msg instanceof WebSocketFrame) {
+ handleWebSocketFrame(ctx, (WebSocketFrame) msg);
+ }
+ }
+
+ /**
+ * Checks if HTTP request method is GET and if is possible to decode HTTP result of request.
+ *
+ * @param ctx ChannelHandlerContext
+ * @param req FullHttpRequest
+ */
+ private void handleHttpRequest(final ChannelHandlerContext ctx, final FullHttpRequest req) {
+ // Handle a bad request.
+ if (!req.decoderResult().isSuccess()) {
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
+ return;
+ }
+
+ // Allow only GET methods.
+ if (req.method() != GET) {
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
+ return;
+ }
+
+ final String streamName = Notificator.createStreamNameFromUri(req.uri());
+ if (streamName.contains(RestconfImpl.DATA_SUBSCR)) {
+ final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+ if (listener != null) {
+ listener.addSubscriber(ctx.channel());
+ LOG.debug("Subscriber successfully registered.");
+ } else {
+ LOG.error("Listener for stream with name '{}' was not found.", streamName);
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR));
+ }
+ } else if (streamName.contains(RestconfImpl.NOTIFICATION_STREAM)) {
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (listeners != null && !listeners.isEmpty()) {
+ for (final NotificationListenerAdapter listener : listeners) {
+ listener.addSubscriber(ctx.channel());
+ LOG.debug("Subscriber successfully registered.");
+ }
+ } else {
+ LOG.error("Listener for stream with name '{}' was not found.", streamName);
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR));
+ }
+ }
+
+ // Handshake
+ final WebSocketServerHandshakerFactory wsFactory =
+ new WebSocketServerHandshakerFactory(getWebSocketLocation(req),
+ null, false);
+ this.handshaker = wsFactory.newHandshaker(req);
+ if (this.handshaker == null) {
+ WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
+ } else {
+ this.handshaker.handshake(ctx.channel(), req);
+ }
+ }
+
+ /**
+ * Checks response status, send response and close connection if necessary.
+ *
+ * @param ctx ChannelHandlerContext
+ * @param req HttpRequest
+ * @param res FullHttpResponse
+ */
+ private static void sendHttpResponse(final ChannelHandlerContext ctx, final HttpRequest req,
+ final FullHttpResponse res) {
+ // Generate an error page if response getStatus code is not OK (200).
+ final boolean notOkay = !OK.equals(res.status());
+ if (notOkay) {
+ res.content().writeCharSequence(res.status().toString(), CharsetUtil.UTF_8);
+ setContentLength(res, res.content().readableBytes());
+ }
+
+ // Send the response and close the connection if necessary.
+ final ChannelFuture f = ctx.channel().writeAndFlush(res);
+ if (notOkay || !isKeepAlive(req)) {
+ f.addListener(ChannelFutureListener.CLOSE);
+ }
+ }
+
+ /**
+ * Handles web socket frame.
+ *
+ * @param ctx {@link ChannelHandlerContext}
+ * @param frame {@link WebSocketFrame}
+ */
+ private void handleWebSocketFrame(final ChannelHandlerContext ctx, final WebSocketFrame frame) {
+ if (frame instanceof CloseWebSocketFrame) {
+ this.handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
+ final String streamName = Notificator.createStreamNameFromUri(((CloseWebSocketFrame) frame).reasonText());
+ if (streamName.contains(RestconfImpl.DATA_SUBSCR)) {
+ final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+ if (listener != null) {
+ listener.removeSubscriber(ctx.channel());
+ LOG.debug("Subscriber successfully registered.");
+
+ Notificator.removeListenerIfNoSubscriberExists(listener);
+ }
+ } else if (streamName.contains(RestconfImpl.NOTIFICATION_STREAM)) {
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (listeners != null && !listeners.isEmpty()) {
+ for (final NotificationListenerAdapter listener : listeners) {
+ listener.removeSubscriber(ctx.channel());
+ }
+ }
+ }
+ return;
+ } else if (frame instanceof PingWebSocketFrame) {
+ ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
+ return;
+ }
+ }
+
+ @Override
+ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
+ ctx.close();
+ }
+
+ /**
+ * Get web socket location from HTTP request.
+ *
+ * @param req HTTP request from which the location will be returned
+ * @return String representation of web socket location.
+ */
+ private static String getWebSocketLocation(final HttpRequest req) {
+ return "ws://" + req.headers().get(HOST) + req.uri();
+ }
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerInitializer.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerInitializer.java
new file mode 100644
index 0000000..365e982
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerInitializer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.streams.websockets;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+
+/**
+ * {@link WebSocketServerInitializer} is used to setup the {@link ChannelPipeline} of a {@link io.netty.channel.Channel}
+ * .
+ */
+public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
+
+ @Override
+ protected void initChannel(final SocketChannel ch) {
+ ChannelPipeline pipeline = ch.pipeline();
+ pipeline.addLast("codec-http", new HttpServerCodec());
+ pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
+ pipeline.addLast("handler", new WebSocketServerHandler());
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilder.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilder.java
new file mode 100644
index 0000000..42defe5
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/DatastoreIdentifierBuilder.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.DatastoreIdentifier.Enumeration;
+
+
+/**
+ **/
+public class DatastoreIdentifierBuilder {
+
+ public static DatastoreIdentifier getDefaultInstance(final String defaultValue) {
+ return new DatastoreIdentifier(Enumeration.valueOf(defaultValue));
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilder.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilder.java
new file mode 100644
index 0000000..dfd0aeb
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/ModuleRevisionBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules;
+
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules.Module.Revision;
+
+/**
+ * The purpose of generated class in src/main/java for Union types is to create
+ * new instances of unions from a string representation. In some cases it is
+ * very difficult to automate it since there can be unions such as (uint32 -
+ * uint16), or (string - uint32).
+ *
+ * The reason behind putting it under src/main/java is: This class is generated
+ * in form of a stub and needs to be finished by the user. This class is
+ * generated only once to prevent loss of user code.
+ *
+ */
+public class ModuleRevisionBuilder {
+
+ public static Revision getDefaultInstance(java.lang.String defaultValue) {
+ return RevisionBuilder.getDefaultInstance(defaultValue);
+ }
+
+}
diff --git a/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilder.java b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilder.java
new file mode 100644
index 0000000..d09646a
--- /dev/null
+++ b/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/restconf/rev131019/restconf/restconf/modules/RevisionBuilder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules;
+
+import java.util.regex.Pattern;
+
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.RevisionIdentifier;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev131019.restconf.restconf.modules.Module.Revision;
+
+/**
+**/
+public class RevisionBuilder {
+
+ /**
+ * Defines the pattern for revisions. NOTE: This pattern will likely be
+ * updated in future versions of the ietf and should be adjusted accordingly
+ */
+ private static final Pattern REVISION_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
+
+ public static Revision getDefaultInstance(String defaultValue) {
+
+ if (defaultValue != null) {
+ if (REVISION_PATTERN.matcher(defaultValue).matches()) {
+ RevisionIdentifier id = new RevisionIdentifier(defaultValue);
+ return new Revision(id);
+ }
+ if (defaultValue.isEmpty()) {
+ return new Revision(defaultValue);
+ }
+ }
+
+ throw new IllegalArgumentException("Cannot create Revision from " + defaultValue
+ + ". Default value does not match pattern " + REVISION_PATTERN.pattern()
+ + " or empty string.");
+ }
+
+}