* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.rest.impl;
+package org.opendaylight.restconf.common.patch;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.restconf.impl;
+package org.opendaylight.restconf.common.patch;
import com.google.common.base.Preconditions;
import java.util.List;
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.restconf.impl;
+package org.opendaylight.restconf.common.patch;
/**
* Each YANG patch edit specifies one edit operation on the target data
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.restconf.impl;
+package org.opendaylight.restconf.common.patch;
import com.google.common.base.Preconditions;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.restconf.impl;
+package org.opendaylight.restconf.common.patch;
import java.util.List;
import org.opendaylight.restconf.common.errors.RestconfError;
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.sal.restconf.impl;
+package org.opendaylight.restconf.common.patch;
import java.util.List;
import org.opendaylight.restconf.common.errors.RestconfError;
* 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;
+package org.opendaylight.restconf.common.util;
import java.util.ArrayList;
import java.util.Collections;
* 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;
+package org.opendaylight.restconf.common.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.events.StartElement;
-import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO;
-import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
-import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.Predicate;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
public final class RestUtil {
* 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.restconf.utils.schema.context;
+package org.opendaylight.restconf.common.util;
import java.util.Collection;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-package org.opendaylight.netconf.md.sal.rest.common;
+package org.opendaylight.restconf.common.validation;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import java.text.ParseException;
import java.util.Date;
import java.util.Iterator;
-import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
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.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
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.Patch;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.base.services.api.RestconfOperationsService;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.Patch;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.restful.services.api.RestconfDataService;
import org.opendaylight.restconf.restful.services.api.RestconfInvokeOperationsService;
import org.opendaylight.restconf.restful.services.api.RestconfStreamsSubscriptionService;
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.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
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 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.PatchStatusContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusEntity;
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;
import javax.xml.stream.XMLStreamWriter;
import org.opendaylight.netconf.sal.rest.api.Draft02;
import org.opendaylight.netconf.sal.rest.api.RestconfService;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusEntity;
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 })
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.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+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 {
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.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEditOperation;
+import org.opendaylight.restconf.common.patch.PatchEntity;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+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.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.netconf.sal.rest.api.Draft02.RestConfModule;
-import org.opendaylight.netconf.sal.rest.impl.RestUtil;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.util.RestUtil;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import java.util.List;
import java.util.Map;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
-import org.opendaylight.netconf.sal.rest.impl.RestUtil;
import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
-import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
-import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.Predicate;
+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.Codec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
-import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
import org.opendaylight.netconf.sal.rest.api.Draft02;
import org.opendaylight.netconf.sal.rest.api.RestconfService;
import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
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.NotificationOutputTypeGrouping.NotificationOutputType;
import org.opendaylight.yangtools.yang.common.QName;
import javax.ws.rs.core.UriInfo;
import org.opendaylight.netconf.sal.rest.api.RestconfService;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
public class StatisticsRestconfServiceWrapper implements RestconfService {
/**
* Provider for restconf draft18.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestConnectorProvider implements RestConnector, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(RestConnectorProvider.class);
private SchemaContextHandler schemaCtxHandler;
- public RestConnectorProvider(DOMDataBroker domDataBroker, SchemaService schemaService, DOMRpcService rpcService,
- DOMNotificationService notificationService, DOMMountPointService mountPointService) {
+ public RestConnectorProvider(final DOMDataBroker domDataBroker, final SchemaService schemaService,
+ final DOMRpcService rpcService, final DOMNotificationService notificationService,
+ final DOMMountPointService mountPointService) {
this.schemaService = Preconditions.checkNotNull(schemaService);
this.rpcService = Preconditions.checkNotNull(rpcService);
this.notificationService = Preconditions.checkNotNull(notificationService);
import org.opendaylight.yangtools.yang.model.api.UsesNode;
/**
- * Special case only use by GET restconf/operations (since moment of old Yang
- * parser and old yang model API removal) to build and use fake container for
- * module.
+ * Special case only use by GET restconf/operations (since moment of old Yang parser and old yang model API
+ * removal) to build and use fake container for module.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
class FakeContainerSchemaNode implements ContainerSchemaNode {
static final SchemaPath PATH =
SchemaPath.create(true, QName.create(FakeRestconfModule.QNAME, "operations").intern());
import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
import org.opendaylight.yangtools.yang.model.api.UsesNode;
+/**
+ * Special case only use by GET restconf/operations (since moment of old Yang parser and old yang model API
+ * removal) to build and use fake container for module.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
final class FakeImportedModule extends ForwardingObject implements Module {
private final Module delegate;
import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
/**
- * Special case only use by GET restconf/operations (since moment of old Yang
- * parser and old yang model API removal) to build and use fake leaf like child
- * in container.
+ * Special case only use by GET restconf/operations (since moment of old Yang parser and old yang model API
+ * removal) to build and use fake leaf like child in container.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
final class FakeLeafSchemaNode implements LeafSchemaNode {
private final SchemaPath path;
/**
* Fake {@link ModuleImport} implementation used to attach corrent prefix mapping to fake RPCs.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
* @author Robert Varga
*/
+@Deprecated
final class FakeModuleImport implements ModuleImport {
private final Module module;
import org.opendaylight.yangtools.yang.model.api.UsesNode;
/**
- * Special case only use by GET restconf/operations (since moment of old Yang
- * parser and old yang model API removal) to build and use fake module to create
- * new schema context.
+ * Special case only use by GET restconf/operations (since moment of old Yang parser and old yang model API
+ * removal) to build and use fake module to create new schema context.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
final class FakeRestconfModule implements Module {
static final QNameModule QNAME;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+/**
+ * Implementation of RestconfService.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
public class RestconfImpl implements RestconfService {
private final SchemaContextHandler schemaContextHandler;
/**
* Implementation of {@link RestconfOperationsService}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfOperationsServiceImpl implements RestconfOperationsService {
private static final Logger LOG = LoggerFactory.getLogger(RestconfOperationsServiceImpl.class);
/**
* Implementation of {@link RestconfSchemaService}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfSchemaServiceImpl implements RestconfSchemaService {
private final SchemaContextHandler schemaContextHandler;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
- * This class creates {@link SoftReference} of actual {@link SchemaContext}
- * object and even if the {@link SchemaContext} changes, this will be sticks
- * reference to the old {@link SchemaContext} and provides work with the old
- * {@link SchemaContext}.
+ * This class creates {@link SoftReference} of actual {@link SchemaContext} object and even if the
+ * {@link SchemaContext} changes, this will be sticks reference to the old {@link SchemaContext} and provides
+ * work with the old {@link SchemaContext}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class SchemaContextRef {
private final SoftReference<SchemaContext> schemaContextRef;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.base.services.api.BaseServicesWrapper;
import org.opendaylight.restconf.base.services.api.RestconfOperationsService;
import org.opendaylight.restconf.base.services.api.RestconfSchemaService;
import org.opendaylight.restconf.base.services.impl.RestconfOperationsServiceImpl;
import org.opendaylight.restconf.base.services.impl.RestconfSchemaServiceImpl;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.common.schema.SchemaExportContext;
import org.opendaylight.restconf.handlers.DOMDataBrokerHandler;
import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
* <li>{@link TransactionServicesWrapper}
* </ul>
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
@Path("/")
public class ServicesWrapperImpl implements BaseServicesWrapper, TransactionServicesWrapper {
/**
* Implementation of {@link DOMDataBrokerHandler}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ *
*/
+@Deprecated
public class DOMDataBrokerHandler implements Handler<DOMDataBroker> {
private final DOMDataBroker broker;
/**
* Implementation of {@link DOMMountPointServiceHandler}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class DOMMountPointServiceHandler implements Handler<DOMMountPointService> {
private final DOMMountPointService domMountPointService;
* Handler for handling object prepared by provider for Restconf services.
*
* @param <T>
- * specific type go object for handling it
+ * specific type go object for handling it
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
interface Handler<T> {
/**
* @param object
* new object to update old object
*/
- default void update(T object) {}
+ default void update(final T object) {}
}
import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
+/**
+ * Handling DOMNotificationService.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
public class NotificationServiceHandler implements Handler<DOMNotificationService> {
private final DOMNotificationService notificationService;
/**
* Implementation of {@link RpcServiceHandler}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RpcServiceHandler implements Handler<DOMRpcService> {
private final DOMRpcService rpcService;
/**
* Implementation of {@link SchemaContextHandler}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class SchemaContextHandler implements SchemaContextListenerHandler {
private static final Logger LOG = LoggerFactory.getLogger(SchemaContextHandler.class);
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
+/**
+ * SchemaContextListener handler.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
interface SchemaContextListenerHandler extends Handler<SchemaContext>, SchemaContextListener {
}
/**
* Implementation of {@link TransactionChainHandler}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class TransactionChainHandler implements Handler<DOMTransactionChain> {
private DOMTransactionChain transactionChain;
*/
package org.opendaylight.restconf.jersey.providers;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
/**
* Common superclass for readers producing {@link PatchContext}.
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
import org.opendaylight.restconf.Rfc8040;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+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.utils.RestconfConstants;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
import org.opendaylight.restconf.Rfc8040;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+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.utils.RestconfConstants;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
- * Codec for identifier to serialize {@link YangInstanceIdentifier} to
- * {@link String} and deserialize {@link String} to
- * {@link YangInstanceIdentifier}.
+ * Codec for identifier to serialize {@link YangInstanceIdentifier} to {@link String} and deserialize
+ * {@link String} to {@link YangInstanceIdentifier}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class IdentifierCodec {
private IdentifierCodec() {
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
-import org.opendaylight.netconf.sal.rest.impl.RestUtil;
import org.opendaylight.netconf.sal.restconf.impl.RestCodec;
import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.restconf.common.util.RestconfSchemaUtil;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
import org.opendaylight.restconf.utils.RestconfConstants;
import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
-import org.opendaylight.restconf.utils.schema.context.RestconfSchemaUtil;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
/**
- * Deserializer for {@link String} to {@link YangInstanceIdentifier} for
- * restconf.
+ * Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class YangInstanceIdentifierDeserializer {
private YangInstanceIdentifierDeserializer() {
/**
* Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class YangInstanceIdentifierSerializer {
private YangInstanceIdentifierSerializer() {
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import org.opendaylight.netconf.sal.rest.impl.Patch;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.Rfc8040;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.Patch;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.utils.RestconfConstants;
/**
- * The "{+restconf}/data" subtree represents the datastore resource type, which
- * is a collection of configuration data and state data nodes.
+ * The "{+restconf}/data" subtree represents the datastore resource type, which is a collection of
+ * configuration data and state data nodes.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public interface RestconfDataService {
/**
import org.opendaylight.restconf.utils.RestconfConstants;
/**
- * An operation resource represents a protocol operation defined with the YANG
- * "rpc" statement. It is invoked using a POST method on the operation resource.
+ * An operation resource represents a protocol operation defined with the YANG "rpc" statement. It is invoked
+ * using a POST method on the operation resource.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public interface RestconfInvokeOperationsService {
/**
/**
* Subscribing to streams.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public interface RestconfStreamsSubscriptionService {
/**
* <li>{@link RestconfStreamsSubscriptionService}
* </ul>
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public interface TransactionServicesWrapper
extends RestconfDataService, RestconfInvokeOperationsService, RestconfStreamsSubscriptionService {
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
import org.opendaylight.restconf.common.context.WriterParameters;
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.references.SchemaContextRef;
import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
import org.opendaylight.restconf.handlers.SchemaContextHandler;
/**
* Implementation of {@link RestconfDataService}.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfDataServiceImpl implements RestconfDataService {
private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
/**
* Implementation of {@link RestconfInvokeOperationsService}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfInvokeOperationsServiceImpl implements RestconfInvokeOperationsService {
private final RpcServiceHandler rpcServiceHandler;
/**
* Implementation of {@link RestconfStreamsSubscriptionService}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSubscriptionService {
private static final Logger LOG = LoggerFactory.getLogger(RestconfStreamsSubscriptionServiceImpl.class);
/**
* This class represent delegation wrapper for transaction variables.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class TransactionVarsWrapper {
private final InstanceIdentifierContext<?> instanceIdentifier;
/**
* Util class for streams.
- *
* <ul>
* <li>create stream
* <li>subscribe
* </ul>
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class CreateStreamUtil {
private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
/**
* Util class for delete specific data in config DS.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class DeleteDataTransactionUtil {
private DeleteDataTransactionUtil() {
/**
* Add callback for future objects and result set to the data factory.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
final class FutureCallbackTx {
private static final Logger LOG = LoggerFactory.getLogger(FutureCallbackTx.class);
final T result = listenableFuture.checkedGet();
dataFactory.setResult(result);
LOG.trace("Transaction({}) SUCCESSFUL", txType);
- } catch (Exception e) {
+ } catch (final Exception e) {
dataFactory.setFailureStatus();
LOG.warn("Transaction({}) FAILED!", txType, e);
if (e instanceof DOMRpcException) {
*/
package org.opendaylight.restconf.restful.utils;
+/**
+ * Future data factory.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
class FutureDataFactory<T> {
protected T result;
import org.apache.commons.lang3.builder.Builder;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+/**
+ * NormalizedNode factory.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
class NormalizedNodeFactory extends FutureDataFactory<Optional<NormalizedNode<?, ?>>>
implements Builder<NormalizedNode<?, ?>> {
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+/**
+ * URI parameters.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
class ParametersUtil {
private ParametersUtil() {
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusEntity;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.restconf.common.references.SchemaContextRef;
import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant.PatchData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * PatchDataTransaction util.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
public final class PatchDataTransactionUtil {
private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
*/
package org.opendaylight.restconf.restful.utils;
+import com.google.common.base.Optional;
import com.google.common.util.concurrent.CheckedFuture;
import java.net.URI;
import javax.ws.rs.core.Response;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
-import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
/**
* Util class to post data to DS.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class PostDataTransactionUtil {
private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
final boolean before, final DOMTransactionChain domTransactionChain) {
rwTransaction.delete(datastore, path.getParent().getParent());
final InstanceIdentifierContext<?> instanceIdentifier =
- ControllerContext.getInstance().toInstanceIdentifier(point);
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
int lastItemPosition = 0;
for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
final DOMTransactionChain domTransactionChain) {
rwTransaction.delete(datastore, path.getParent().getParent());
final InstanceIdentifierContext<?> instanceIdentifier =
- ControllerContext.getInstance().toInstanceIdentifier(point);
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
int lastItemPosition = 0;
for (final MapEntryNode mapEntryNode : readList.getValue()) {
if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
*/
package org.opendaylight.restconf.restful.utils;
+import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.CheckedFuture;
import java.util.List;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
-import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
-import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.restconf.utils.parser.ParserIdentifier;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
/**
* Util class for put data to DS.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class PutDataTransactionUtil {
/**
public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
final TransactionVarsWrapper transactionNode, final String insert, final String point) {
final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
+ final SchemaContext schemaContext = schemaCtxRef.get();
final ResponseFactory responseFactory = new ResponseFactory(
- ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode));
- final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaCtxRef.get(),
+ ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode,
+ schemaContext));
+ final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
transactionNode.getTransactionChain(), payload.getData(), insert, point);
FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
return responseFactory.build();
final TransactionVarsWrapper transactionNode =
new TransactionVarsWrapper(iid, null, domTransactionChain);
final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
- .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode);
+ .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
return readData;
}
final OrderedLeafSetNode<?> readLeafList, final boolean before) {
rwTransaction.delete(datastore, path.getParent());
final InstanceIdentifierContext<?> instanceIdentifier =
- ControllerContext.getInstance().toInstanceIdentifier(point);
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
int lastItemPosition = 0;
for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
final OrderedMapNode readList, final boolean before) {
writeTx.delete(datastore, path.getParent());
final InstanceIdentifierContext<?> instanceIdentifier =
- ControllerContext.getInstance().toInstanceIdentifier(point);
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
int lastItemPosition = 0;
for (final MapEntryNode mapEntryNode : readList.getValue()) {
if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
-import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.context.WriterParameters;
* <li>all (config + state)
* </ul>
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class ReadDataTransactionUtil {
private ReadDataTransactionUtil() {
/**
* Parse parameters from URI request and check their types and values.
*
- *
* @param identifier
* {@link InstanceIdentifierContext}
* @param uriInfo
* Read specific type of data from data store via transaction.
*
* @param valueOfContent
- * type of data to read (config, state, all)
+ * type of data to read (config, state, all)
* @param transactionNode
- * {@link TransactionVarsWrapper} - wrapper for variables
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param ctx
+ * schema context
* @return {@link NormalizedNode}
*/
@Nullable
public static NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
- @Nonnull final TransactionVarsWrapper transactionNode) {
- return readData(valueOfContent, transactionNode, null);
+ @Nonnull final TransactionVarsWrapper transactionNode, final SchemaContext ctx) {
+ return readData(valueOfContent, transactionNode, null, ctx);
}
/**
* Read specific type of data from data store via transaction.
*
* @param valueOfContent
- * type of data to read (config, state, all)
+ * type of data to read (config, state, all)
* @param transactionNode
- * {@link TransactionVarsWrapper} - wrapper for variables
+ * {@link TransactionVarsWrapper} - wrapper for variables
* @param withDefa
- * vaule of with-defaults parameter
+ * vaule of with-defaults parameter
+ * @param ctx
+ * schema context
* @return {@link NormalizedNode}
*/
@Nullable
public static NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
- @Nonnull final TransactionVarsWrapper transactionNode, final String withDefa) {
+ @Nonnull final TransactionVarsWrapper transactionNode, final String withDefa, final SchemaContext ctx) {
switch (valueOfContent) {
case RestconfDataServiceConstant.ReadData.CONFIG:
transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
return readDataViaTransaction(transactionNode);
} else {
return prepareDataByParamWithDef(readDataViaTransaction(transactionNode),
- transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa);
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx);
}
case RestconfDataServiceConstant.ReadData.NONCONFIG:
transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
return readDataViaTransaction(transactionNode);
case RestconfDataServiceConstant.ReadData.ALL:
- return readAllData(transactionNode, withDefa);
+ return readAllData(transactionNode, withDefa, ctx);
default:
throw new RestconfDocumentedException(
public static NormalizedNode<?, ?> readData(final String identifier, final String content,
final TransactionVarsWrapper transactionNode, final String withDefa,
final SchemaContextRef schemaContextRef, final UriInfo uriInfo) {
+ final SchemaContext schemaContext = schemaContextRef.get();
if (identifier.contains(STREAMS_PATH) && !identifier.contains(STREAM_PATH_PART)) {
final DOMDataReadWriteTransaction wTx = transactionNode.getTransactionChain().newReadWriteTransaction();
- final SchemaContext schemaContext = schemaContextRef.get();
final boolean exist = SubscribeToStreamUtil.checkExist(schemaContext, wTx);
for (final NotificationDefinition notificationDefinition : schemaContextRef.get().getNotifications()) {
}
SubscribeToStreamUtil.submitData(wTx);
}
- return readData(content, transactionNode, withDefa);
+ return readData(content, transactionNode, withDefa, schemaContext);
}
private static NormalizedNode<?, ?> prepareDataByParamWithDef(final NormalizedNode<?, ?> result,
- final YangInstanceIdentifier path, final String withDefa) {
+ final YangInstanceIdentifier path, final String withDefa, final SchemaContext ctx) {
boolean trim;
switch (withDefa) {
case "trim":
throw new RestconfDocumentedException("");
}
- final SchemaContext ctx = ControllerContext.getInstance().getGlobalSchema();
final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
if (result instanceof ContainerNode) {
* Read config and state data, then map them.
*
* @param transactionNode
- * {@link TransactionVarsWrapper} - wrapper for variables
- * @param withDefa with-defaults parameter
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param withDefa
+ * with-defaults parameter
+ * @param ctx
+ * schema context
* @return {@link NormalizedNode}
*/
@Nullable
private static NormalizedNode<?, ?> readAllData(@Nonnull final TransactionVarsWrapper transactionNode,
- final String withDefa) {
+ final String withDefa, final SchemaContext ctx) {
// PREPARE STATE DATA NODE
transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(transactionNode);
configDataNode = readDataViaTransaction(transactionNode);
} else {
configDataNode = prepareDataByParamWithDef(readDataViaTransaction(transactionNode),
- transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa);
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx);
}
// if no data exists
/**
* Common util class for resolve enum from String.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class ResolveEnumUtil {
private ResolveEnumUtil() {
import org.apache.commons.lang3.builder.Builder;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+/**
+ * Response factory.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
final class ResponseFactory extends FutureDataFactory<Void> implements Builder<Response> {
private ResponseBuilder responseBuilder;
/**
* Constants for RestconfDataService.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfDataServiceConstant {
public static final QName NETCONF_BASE_QNAME;
/**
* Util class for rpc.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public class RestconfInvokeOperationsUtil {
private static final Logger LOG = LoggerFactory.getLogger(RestconfInvokeOperationsUtil.class);
/**
* Constants for streams.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfStreamsConstants {
private static final Logger LOG = LoggerFactory.getLogger(RestconfStreamsConstants.class);
import org.apache.commons.lang3.builder.Builder;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+/**
+ * Rpc result factory.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
public class RpcResultFactory extends FutureDataFactory<DOMRpcResult> implements Builder<DOMRpcResult> {
@Override
/**
* Subscribe to stream util class.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class SubscribeToStreamUtil {
private static final Logger LOG = LoggerFactory.getLogger(SubscribeToStreamUtil.class);
/**
* Util class for common methods of transactions.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class TransactionUtil {
private static final Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
import com.google.common.base.Splitter;
/**
- * {@link Deprecated} move to splitted module restconf-nb-rfc8040. Util class for Restconf constants.
+ * Util class for Restconf constants.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
@Deprecated
public final class RestconfConstants {
public static final String IDENTIFIER = "identifier";
public static final char SLASH = '/';
public static final Splitter SLASH_SPLITTER = Splitter.on(SLASH);
- public static final String DRAFT_PATTERN = "restconf/17";
+ public static final String DRAFT_PATTERN = "restconf/18";
private RestconfConstants() {
throw new UnsupportedOperationException("Util class");
/**
* Util class for constants of mapping node.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfMappingNodeConstants {
public static final String NAME = "name";
/**
* Util class for mapping nodes.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfMappingNodeUtil {
private RestconfMappingNodeUtil() {
/**
* Util class for mapping entry stream.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfMappingStreamConstants {
public static final String DESCRIPTION = "DESCRIPTION_PLACEHOLDER";
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+/**
+ * Parameters parser.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
+ */
+@Deprecated
public class ParserFieldsParameter {
private static final char COLON = ':';
private static final char SEMICOLON = ';';
currentNode,
input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
// call with child node as new start node for one level down
- int closingParenthesis = currentPosition
+ final int closingParenthesis = currentPosition
+ findClosingParenthesis(input.substring(currentPosition + 1));
parseInput(
input.substring(currentPosition + 1, closingParenthesis),
/**
* Util class for parsing identifier.
+ *
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class ParserIdentifier {
private static final Logger LOG = LoggerFactory.getLogger(ParserIdentifier.class);
import org.opendaylight.restconf.parser.builder.YangInstanceIdentifierSerializer;
/**
- * Util class of constants of {@link YangInstanceIdentifierSerializer}
- * and {@link YangInstanceIdentifierDeserializer}.
+ * Util class of constants of {@link YangInstanceIdentifierSerializer} and
+ * {@link YangInstanceIdentifierDeserializer}.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class ParserBuilderConstants {
private ParserBuilderConstants() {
import java.text.ParseException;
import java.util.Date;
import java.util.Iterator;
-import org.opendaylight.netconf.md.sal.rest.common.RestconfValidationUtils;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
/**
* Util class for validations.
*
+ * @deprecated move to splitted module restconf-nb-rfc8040
*/
+@Deprecated
public final class RestconfValidation {
private RestconfValidation() {
import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
import org.opendaylight.netconf.sal.rest.impl.AbstractIdentifierAwareJaxRsProvider;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public abstract class AbstractBodyReaderTest {
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public class TestJsonPatchBodyReader extends AbstractBodyReaderTest {
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.netconf.sal.rest.impl.JsonToPatchBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.netconf.sal.rest.impl.XmlToPatchBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public class TestXmlPatchBodyReader extends AbstractBodyReaderTest {
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.netconf.sal.rest.impl.XmlToPatchBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.netconf.sal.restconf.impl.PutResult;
import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.handlers.SchemaContextHandler;
import org.opendaylight.restconf.handlers.TransactionChainHandler;
import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
import org.junit.Test;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
import org.opendaylight.netconf.sal.restconf.impl.RestCodec.InstanceIdentifierCodecImpl;
+import org.opendaylight.restconf.common.util.IdentityValuesDTO;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.controller.sal.rest.impl.test.providers.TestJsonBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.junit.Test;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.controller.sal.rest.impl.test.providers.TestJsonBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public class JsonPatchBodyReaderTest extends AbstractBodyReaderTest {
import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.controller.sal.rest.impl.test.providers.TestXmlBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.junit.Test;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.controller.sal.rest.impl.test.providers.TestXmlBodyReader;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public class XmlPatchBodyReaderTest extends AbstractBodyReaderTest {
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.CREATE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.DELETE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.REMOVE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.REPLACE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.CheckedFuture;
import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.common.references.SchemaContextRef;
import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
import org.opendaylight.restconf.handlers.SchemaContextHandler;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.MockitoAnnotations.initMocks;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.CREATE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.DELETE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.MERGE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.REMOVE;
-import static org.opendaylight.netconf.sal.restconf.impl.PatchEditOperation.REPLACE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.CREATE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.DELETE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.MERGE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.REMOVE;
+import static org.opendaylight.restconf.common.patch.PatchEditOperation.REPLACE;
import com.google.common.util.concurrent.Futures;
import java.lang.reflect.Field;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
-import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchEntity;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
-import org.opendaylight.netconf.sal.restconf.impl.PatchStatusEntity;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.restconf.common.references.SchemaContextRef;
import org.opendaylight.restconf.handlers.TransactionChainHandler;
import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
.read(LogicalDatastoreType.CONFIGURATION, DATA.path);
doReturn(DATA.path).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.CONFIG;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
assertEquals(DATA.data3, normalizedNode);
}
.read(LogicalDatastoreType.OPERATIONAL, DATA.path);
doReturn(DATA.path).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
assertEquals(DATA.data3, normalizedNode);
}
.read(LogicalDatastoreType.CONFIGURATION, DATA.path2);
doReturn(DATA.path2).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
assertEquals(DATA.data2, normalizedNode);
}
.read(LogicalDatastoreType.OPERATIONAL, DATA.path2);
doReturn(DATA.path2).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.NONCONFIG;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
assertEquals(DATA.data2, normalizedNode);
}
.read(LogicalDatastoreType.OPERATIONAL, DATA.path);
doReturn(DATA.path).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
final ContainerNode checkingData = Builders
.containerBuilder()
.withNodeIdentifier(NODE_IDENTIFIER)
.read(LogicalDatastoreType.OPERATIONAL, DATA.path);
doReturn(DATA.path).when(context).getInstanceIdentifier();
final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(
- RestconfDataServiceConstant.ReadData.ALL, wrapper);
+ RestconfDataServiceConstant.ReadData.ALL, wrapper, schemaContext);
final ContainerNode checkingData = Builders
.containerBuilder()
.withNodeIdentifier(NODE_IDENTIFIER)
.read(LogicalDatastoreType.CONFIGURATION, DATA.path3);
doReturn(DATA.path3).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.ALL;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
final MapNode checkingData = Builders
.mapBuilder()
.withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create("ns", "2016-02-28", "list")))
.read(LogicalDatastoreType.CONFIGURATION, DATA.path2);
doReturn(DATA.path2).when(context).getInstanceIdentifier();
final String valueOfContent = RestconfDataServiceConstant.ReadData.CONFIG;
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, wrapper);
+ final NormalizedNode<?, ?> normalizedNode =
+ ReadDataTransactionUtil.readData(valueOfContent, wrapper, schemaContext);
assertNull(normalizedNode);
}
public void readDataFailTest() {
final String valueOfContent = RestconfDataServiceConstant.ReadData.READ_TYPE_TX;
final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(
- valueOfContent, wrapper);
+ valueOfContent, wrapper, schemaContext);
assertNull(normalizedNode);
}
import org.junit.Test;
import org.mockito.Mockito;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.util.RestconfSchemaUtil;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
<version>1.7.0-SNAPSHOT</version>
<packaging>bundle</packaging>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>netconf-parent</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-artifacts</artifactId>
+ <version>0.7.0-SNAPSHOT</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-artifacts</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-artifacts</artifactId>
+ <version>1.7.0-SNAPSHOT</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
<groupId>org.opendaylight.netconf</groupId>
<artifactId>restconf-common</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>ietf-yang-library</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-data-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-model-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-codec-xml</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-codec-gson</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>ietf-yang-types-20130715</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-codec-http</artifactId>
+ </dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.0</version>
+ </dependency>
</dependencies>
</project>
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMDataBrokerHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.NotificationServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.RpcServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.nb.rfc8040.services.wrapper.ServicesWrapperImpl;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provider for restconf draft18.
+ *
+ */
+public class RestConnectorProvider implements RestconfConnector, AutoCloseable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestConnectorProvider.class);
+
+ public static final TransactionChainListener TRANSACTION_CHAIN_LISTENER = new TransactionChainListener() {
+ @Override
+ public void onTransactionChainFailed(final TransactionChain<?, ?> chain,
+ final AsyncTransaction<?, ?> transaction, final Throwable cause) {
+ LOG.warn("TransactionChain({}) {} FAILED!", chain, transaction.getIdentifier(), cause);
+ resetTransactionChainForAdapaters(chain);
+ throw new RestconfDocumentedException("TransactionChain(" + chain + ") not committed correctly", cause);
+ }
+
+ @Override
+ public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
+ LOG.trace("TransactionChain({}) {} SUCCESSFUL", chain);
+ }
+ };
+
+ private static TransactionChainHandler transactionChainHandler;
+ private static DOMDataBroker dataBroker;
+ private static DOMMountPointServiceHandler mountPointServiceHandler;
+
+ private final SchemaService schemaService;
+ private final DOMRpcService rpcService;
+ private final DOMNotificationService notificationService;
+ private final DOMMountPointService mountPointService;
+ private ListenerRegistration<SchemaContextListener> listenerRegistration;
+
+ private SchemaContextHandler schemaCtxHandler;
+
+ public RestConnectorProvider(final DOMDataBroker domDataBroker, final SchemaService schemaService, final DOMRpcService rpcService,
+ final DOMNotificationService notificationService, final DOMMountPointService mountPointService) {
+ this.schemaService = Preconditions.checkNotNull(schemaService);
+ this.rpcService = Preconditions.checkNotNull(rpcService);
+ this.notificationService = Preconditions.checkNotNull(notificationService);
+ this.mountPointService = Preconditions.checkNotNull(mountPointService);
+
+ RestConnectorProvider.dataBroker = Preconditions.checkNotNull(domDataBroker);
+ }
+
+ public void start() {
+ final ServicesWrapperImpl wrapperServices = ServicesWrapperImpl.getInstance();
+
+ mountPointServiceHandler = new DOMMountPointServiceHandler(mountPointService);
+
+ final DOMDataBrokerHandler brokerHandler = new DOMDataBrokerHandler(dataBroker);
+
+ RestConnectorProvider.transactionChainHandler = new TransactionChainHandler(dataBroker
+ .createTransactionChain(RestConnectorProvider.TRANSACTION_CHAIN_LISTENER));
+
+ this.schemaCtxHandler = new SchemaContextHandler(transactionChainHandler);
+ this.listenerRegistration = schemaService.registerSchemaContextListener(this.schemaCtxHandler);
+
+ final RpcServiceHandler rpcServiceHandler = new RpcServiceHandler(rpcService);
+
+ final NotificationServiceHandler notificationServiceHandler =
+ new NotificationServiceHandler(notificationService);
+
+ wrapperServices.setHandlers(this.schemaCtxHandler, RestConnectorProvider.mountPointServiceHandler,
+ RestConnectorProvider.transactionChainHandler, brokerHandler, rpcServiceHandler,
+ notificationServiceHandler);
+ }
+
+ public DOMMountPointServiceHandler getMountPointServiceHandler() {
+ return mountPointServiceHandler;
+ }
+
+ /**
+ * After {@link TransactionChain} failed, this updates {@link TransactionChainHandler} with new transaction chain.
+ *
+ * @param chain
+ * old {@link TransactionChain}
+ */
+ public static void resetTransactionChainForAdapaters(final TransactionChain<?, ?> chain) {
+ LOG.trace("Resetting TransactionChain({})", chain);
+ chain.close();
+ RestConnectorProvider.transactionChainHandler.update(
+ Preconditions.checkNotNull(dataBroker).createTransactionChain(
+ RestConnectorProvider.TRANSACTION_CHAIN_LISTENER)
+ );
+ }
+
+ /**
+ * Get current {@link DOMMountPointService} from {@link DOMMountPointServiceHandler}.
+ * @return {@link DOMMountPointService}
+ */
+ public static DOMMountPointService getMountPointService() {
+ return mountPointServiceHandler.get();
+ }
+
+ @Override
+ public void close() throws Exception {
+ // close registration
+ if (this.listenerRegistration != null) {
+ this.listenerRegistration.close();
+ }
+
+ // close transaction chain
+ if (transactionChainHandler != null && transactionChainHandler.get() != null) {
+ transactionChainHandler.get().close();
+ }
+
+ transactionChainHandler = null;
+ mountPointServiceHandler = null;
+ dataBroker = null;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040;
+
+/*
+ * This is a simple dummy interface to allow us to create instances of RestconfProvider
+ * via the config subsystem.
+ */
+public interface RestconfConnector {
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.codecs;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+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.Codec;
+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.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.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+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.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.SchemaContext;
+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 class RestCodec {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class);
+
+ private RestCodec() {
+ }
+
+ public static final Codec<Object, Object> from(final TypeDefinition<?> typeDefinition,
+ final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
+ return new ObjectCodec(typeDefinition, mountPoint, schemaContext);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final class ObjectCodec implements Codec<Object, Object> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ObjectCodec.class);
+
+ public static final Codec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();
+ private final Codec instanceIdentifier;
+ private final Codec identityrefCodec;
+
+ private final TypeDefinition<?> type;
+
+ private final SchemaContext schemaContext;
+
+ private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint,
+ final SchemaContext schemaContext) {
+ this.schemaContext = schemaContext;
+ this.type = RestUtil.resolveBaseTypeFrom(typeDefinition);
+ if (this.type instanceof IdentityrefTypeDefinition) {
+ this.identityrefCodec = new IdentityrefCodecImpl(mountPoint, schemaContext);
+ } else {
+ this.identityrefCodec = null;
+ }
+ if (this.type instanceof InstanceIdentifierTypeDefinition) {
+ this.instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint, schemaContext);
+ } else {
+ this.instanceIdentifier = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object deserialize(final Object input) {
+ try {
+ if (this.type instanceof IdentityrefTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return this.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));
+ }
+ return null;
+ } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
+ if (input instanceof IdentityValuesDTO) {
+ return this.instanceIdentifier.deserialize(input);
+ } else {
+ final StringModuleInstanceIdentifierCodec codec =
+ new StringModuleInstanceIdentifierCodec(schemaContext);
+ return codec.deserialize((String) input);
+ }
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(this.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 \"" + this.type.getQName().getLocalName()
+ + "\" is not implemented yet.");
+ 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 " + String.valueOf(input),
+ e);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object serialize(final Object input) {
+ try {
+ if (this.type instanceof IdentityrefTypeDefinition) {
+ return this.identityrefCodec.serialize(input);
+ } else if (this.type instanceof LeafrefTypeDefinition) {
+ return LEAFREF_DEFAULT_CODEC.serialize(input);
+ } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
+ return this.instanceIdentifier.serialize(input);
+ } else {
+ final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
+ TypeDefinitionAwareCodec.from(this.type);
+ if (typeAwarecodec != null) {
+ return typeAwarecodec.serialize(input);
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Codec for type \"" + this.type.getQName().getLocalName()
+ + "\" is not implemented yet.");
+ }
+ 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 " + String.valueOf(input),
+ e);
+ return input;
+ }
+ }
+
+ }
+
+ public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class);
+
+ private final DOMMountPoint mountPoint;
+
+ private final SchemaContext schemaContext;
+
+ public IdentityrefCodecImpl(final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
+ this.mountPoint = mountPoint;
+ this.schemaContext = schemaContext;
+ }
+
+ @Override
+ public IdentityValuesDTO serialize(final QName data) {
+ return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
+ }
+
+ @Override
+ public QName deserialize(final IdentityValuesDTO data) {
+ final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
+ final Module module =
+ getModuleByNamespace(valueWithNamespace.getNamespace(), this.mountPoint, schemaContext);
+ if (module == null) {
+ 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 SchemaContext schemaContext;
+
+ public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
+ this.mountPoint = mountPoint;
+ this.schemaContext = schemaContext;
+ }
+
+ @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).getKeyValues());
+ 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;
+ }
+
+ @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(), this.mountPoint, schemaContext);
+ 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()));
+ 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);
+ URI validNamespace =
+ resolveValidNamespace(identityValue.getNamespace(), this.mountPoint, schemaContext);
+ final DataSchemaNode node = findInstanceDataChildByNameAndNamespace(
+ parentContainer, identityValue.getValue(), validNamespace);
+ if (node == 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()));
+ return null;
+ }
+ 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()));
+ 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(), this.mountPoint,
+ schemaContext);
+ final DataSchemaNode listKey = findInstanceDataChildByNameAndNamespace(listNode,
+ predicate.getName().getValue(), validNamespace);
+ predicatesMap.put(listKey.getQName(), predicate.getValue());
+ }
+ pathArgument = new NodeIdentifierWithPredicates(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()));
+ 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()));
+ return null;
+ }
+ }
+ }
+
+ return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
+ }
+
+ private static List<Predicate> keyValuesToPredicateList(final Map<QName, Object> keyValues) {
+ final List<Predicate> result = new ArrayList<>();
+ for (final QName qualifiedName : keyValues.keySet()) {
+ final Object value = keyValues.get(qualifiedName);
+ 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 SchemaContext schemaContext) {
+ final URI validNamespace = resolveValidNamespace(namespace, mountPoint, schemaContext);
+ Module module = null;
+ if (mountPoint != null) {
+ module = mountPoint.getSchemaContext().findModuleByNamespace(validNamespace).iterator().next();
+ } else {
+ module = schemaContext.findModuleByNamespace(validNamespace).iterator().next();
+ }
+ if (module == null) {
+ LOG.info("Module for namespace " + validNamespace + " wasn't found.");
+ return null;
+ }
+ return module;
+ }
+
+ private static URI resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
+ URI validNamespace;
+ if (mountPoint != null) {
+ validNamespace = findFirstModuleByName(schemaContext, namespace);
+ } else {
+ validNamespace = findFirstModuleByName(schemaContext, namespace);
+ }
+ if (validNamespace == null) {
+ validNamespace = URI.create(namespace);
+ }
+
+ return validNamespace;
+ }
+
+ private static URI findFirstModuleByName(final SchemaContext schemaContext, final String name) {
+ for (final Module module : schemaContext.getModules()) {
+ if (module.getName().equals(name)) {
+ return module.getNamespace();
+ }
+ }
+ return null;
+ }
+
+ private static DataSchemaNode findInstanceDataChildByNameAndNamespace(final DataNodeContainer container,
+ final String name, final URI namespace) {
+ Preconditions.checkNotNull(namespace);
+
+ final Iterable<DataSchemaNode> result = Iterables.filter(findInstanceDataChildrenByName(container, name),
+ node -> namespace.equals(node.getQName().getNamespace()));
+ return Iterables.getFirst(result, null);
+ }
+
+ private static List<DataSchemaNode> findInstanceDataChildrenByName(final DataNodeContainer container,
+ final String name) {
+ Preconditions.checkNotNull(container);
+ Preconditions.checkNotNull(name);
+
+ final List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<>();
+ collectInstanceDataNodeContainers(instantiatedDataNodeContainers, container, name);
+ return instantiatedDataNodeContainers;
+ }
+
+ private static void collectInstanceDataNodeContainers(final List<DataSchemaNode> potentialSchemaNodes,
+ final DataNodeContainer container, final String name) {
+
+ final Iterable<DataSchemaNode> nodes =
+ Iterables.filter(container.getChildNodes(), node -> name.equals(node.getQName().getLocalName()));
+
+ // Can't combine this loop with the filter above because the filter is
+ // lazily-applied by Iterables.filter.
+ for (final DataSchemaNode potentialNode : nodes) {
+ if (isInstantiatedDataSchema(potentialNode)) {
+ potentialSchemaNodes.add(potentialNode);
+ }
+ }
+
+ final Iterable<ChoiceSchemaNode> choiceNodes =
+ Iterables.filter(container.getChildNodes(), ChoiceSchemaNode.class);
+ final Iterable<Set<ChoiceCaseNode>> map = Iterables.transform(choiceNodes, ChoiceSchemaNode::getCases);
+ for (final ChoiceCaseNode caze : Iterables.concat(map)) {
+ collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name);
+ }
+ }
+
+ private static boolean isInstantiatedDataSchema(final DataSchemaNode node) {
+ return (node instanceof LeafSchemaNode) || (node instanceof LeafListSchemaNode)
+ || (node instanceof ContainerSchemaNode) || (node instanceof ListSchemaNode)
+ || (node instanceof AnyXmlSchemaNode);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.codecs;
+
+import com.google.common.base.Preconditions;
+import java.net.URI;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+
+ private final DataSchemaContextTree dataContextTree;
+ private final SchemaContext context;
+ private final String defaultPrefix;
+
+ public StringModuleInstanceIdentifierCodec(SchemaContext context) {
+ this.context = Preconditions.checkNotNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = "";
+ }
+
+ StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
+ this.context = Preconditions.checkNotNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ this.defaultPrefix = defaultPrefix;
+ }
+
+ @Override
+ protected Module moduleForPrefix(@Nonnull final String prefix) {
+ if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
+ return this.context.findModuleByName(this.defaultPrefix, null);
+ } else {
+ return this.context.findModuleByName(prefix, null);
+ }
+ }
+
+ @Nonnull
+ @Override
+ protected DataSchemaContextTree getDataContextTree() {
+ return this.dataContextTree;
+ }
+
+ @Nullable
+ @Override
+ protected String prefixForNamespace(@Nonnull final URI namespace) {
+ final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
+ return module == null ? null : module.getName();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+
+/**
+ * Implementation of {@link DOMDataBrokerHandler}.
+ *
+ */
+public class DOMDataBrokerHandler implements Handler<DOMDataBroker> {
+
+ private final DOMDataBroker broker;
+
+ public DOMDataBrokerHandler(final DOMDataBroker broker) {
+ this.broker = broker;
+ }
+
+ @Override
+ public DOMDataBroker get() {
+ return this.broker;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+
+/**
+ * Implementation of {@link DOMMountPointServiceHandler}.
+ *
+ */
+public class DOMMountPointServiceHandler implements Handler<DOMMountPointService> {
+
+ private final DOMMountPointService domMountPointService;
+
+ /**
+ * Prepare mount point service for Restconf services.
+ *
+ * @param domMountPointService
+ * mount point service
+ */
+ public DOMMountPointServiceHandler(final DOMMountPointService domMountPointService) {
+ Preconditions.checkNotNull(domMountPointService);
+ this.domMountPointService = domMountPointService;
+ }
+
+ @Override
+ public DOMMountPointService get() {
+ return this.domMountPointService;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+/**
+ * Handler for handling object prepared by provider for Restconf services.
+ *
+ * @param <T>
+ * specific type go object for handling it
+ */
+interface Handler<T> {
+
+ /**
+ * Get prepared object.
+ *
+ * @return T
+ */
+ T get();
+
+ /**
+ * Update object.
+ *
+ * @param object
+ * new object to update old object
+ */
+ default void update(T object) {}
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
+
+public class NotificationServiceHandler implements Handler<DOMNotificationService> {
+
+ private final DOMNotificationService notificationService;
+
+ /**
+ * Set DOMNotificationService.
+ *
+ * @param notificationService
+ * DOMNotificationService
+ */
+ public NotificationServiceHandler(final DOMNotificationService notificationService) {
+ this.notificationService = notificationService;
+ }
+
+ @Override
+ public DOMNotificationService get() {
+ return this.notificationService;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+
+/**
+ * Implementation of {@link RpcServiceHandler}.
+ *
+ */
+public class RpcServiceHandler implements Handler<DOMRpcService> {
+
+ private final DOMRpcService rpcService;
+
+ public RpcServiceHandler(final DOMRpcService rpcService) {
+ this.rpcService = rpcService;
+ }
+
+ @Override
+ public DOMRpcService get() {
+ return this.rpcService;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import com.google.common.base.Preconditions;
+import java.util.Collection;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.RestConnectorProvider;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule;
+import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
+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.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link SchemaContextHandler}.
+ *
+ */
+public class SchemaContextHandler implements SchemaContextListenerHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SchemaContextHandler.class);
+
+ private final TransactionChainHandler transactionChainHandler;
+ private SchemaContext context;
+
+ private int moduleSetId;
+
+ /**
+ * Set module-set-id on initial value - 0.
+ *
+ * @param transactionChainHandler Transaction chain handler
+ */
+ public SchemaContextHandler(final TransactionChainHandler transactionChainHandler) {
+ this.transactionChainHandler = transactionChainHandler;
+ this.moduleSetId = 0;
+ }
+
+ @Override
+ public void onGlobalContextUpdated(final SchemaContext context) {
+ Preconditions.checkNotNull(context);
+ this.context = null;
+ this.context = context;
+ this.moduleSetId++;
+ final Module ietfYangLibraryModule =
+ context.findModuleByNamespaceAndRevision(IetfYangLibrary.URI_MODULE, IetfYangLibrary.DATE);
+ NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>> normNode =
+ RestconfMappingNodeUtil.mapModulesByIetfYangLibraryYang(context.getModules(), ietfYangLibraryModule,
+ context, String.valueOf(this.moduleSetId));
+ putData(normNode);
+
+ final Module monitoringModule =
+ this.context.findModuleByNamespaceAndRevision(MonitoringModule.URI_MODULE, MonitoringModule.DATE);
+ normNode = RestconfMappingNodeUtil.mapCapabilites(monitoringModule);
+ putData(normNode);
+ }
+
+ @Override
+ public SchemaContext get() {
+ return this.context;
+ }
+
+ private void putData(
+ final NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>> normNode) {
+ final DOMDataWriteTransaction wTx = this.transactionChainHandler.get().newWriteOnlyTransaction();
+ wTx.put(LogicalDatastoreType.OPERATIONAL,
+ YangInstanceIdentifier.create(NodeIdentifier.create(normNode.getNodeType())), normNode);
+ try {
+ wTx.submit().checkedGet();
+ } catch (final TransactionCommitFailedException e) {
+ if (!(e.getCause() instanceof ConflictingModificationAppliedException)) {
+ throw new RestconfDocumentedException("Problem occurred while putting data to DS.", e);
+ }
+
+ /*
+ Ignore error when another cluster node is already putting the same data to DS.
+ We expect that cluster is homogeneous and that node was going to write the same data
+ (that means no retry is needed). Transaction chain reset must be invoked to be able
+ to continue writing data with another transaction after failed transaction.
+ This is workaround for bug:
+ https://bugs.opendaylight.org/show_bug.cgi?id=7728
+ */
+ LOG.warn("Ignoring that another cluster node is already putting the same data to DS.", e);
+ RestConnectorProvider.resetTransactionChainForAdapaters(this.transactionChainHandler.get());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
+
+interface SchemaContextListenerHandler extends Handler<SchemaContext>, SchemaContextListener {
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.handlers;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+
+
+/**
+ * Implementation of {@link TransactionChainHandler}.
+ *
+ */
+public class TransactionChainHandler implements Handler<DOMTransactionChain> {
+
+ private DOMTransactionChain transactionChain;
+
+ /**
+ * Prepare transaction chain service for Restconf services.
+ *
+ * @param transactionChain Transaction chain
+ */
+ public TransactionChainHandler(final DOMTransactionChain transactionChain) {
+ Preconditions.checkNotNull(transactionChain);
+ this.transactionChain = transactionChain;
+ }
+
+ @Override
+ public void update(final DOMTransactionChain transactionChain) {
+ Preconditions.checkNotNull(transactionChain);
+ this.transactionChain = transactionChain;
+ }
+
+ @Override
+ public DOMTransactionChain get() {
+ return this.transactionChain;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.references;
+
+import java.lang.ref.SoftReference;
+import java.net.URI;
+import java.util.Date;
+import java.util.Set;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * This class creates {@link SoftReference} of actual {@link SchemaContext}
+ * object and even if the {@link SchemaContext} changes, this will be sticks
+ * reference to the old {@link SchemaContext} and provides work with the old
+ * {@link SchemaContext}.
+ *
+ */
+public final class SchemaContextRef {
+
+ private final SoftReference<SchemaContext> schemaContextRef;
+
+ /**
+ * Create {@link SoftReference} of actual {@link SchemaContext}.
+ *
+ * @param schemaContext
+ * actual {@link SchemaContext}
+ */
+ public SchemaContextRef(final SchemaContext schemaContext) {
+ this.schemaContextRef = new SoftReference<SchemaContext>(schemaContext);
+ }
+
+ /**
+ * Get {@link SchemaContext} from reference.
+ *
+ * @return {@link SchemaContext}
+ */
+ public SchemaContext get() {
+ return this.schemaContextRef.get();
+ }
+
+ /**
+ * Get all modules like {@link Set} of {@link Module} from
+ * {@link SchemaContext}.
+ *
+ * @return {@link Set} of {@link Module}
+ */
+ public Set<Module> getModules() {
+ return get().getModules();
+ }
+
+ /**
+ * Get all modules like {@link Set} of {@link Module} from
+ * {@link SchemaContext} of {@link DOMMountPoint}.
+ *
+ * @param mountPoint
+ * mount point
+ *
+ * @return {@link Set} of {@link Module}
+ */
+ public Set<Module> getModules(final DOMMountPoint mountPoint) {
+ final SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
+ return schemaContext == null ? null : schemaContext.getModules();
+ }
+
+ /**
+ * Get {@link Module} by ietf-restconf qname from
+ * {@link Rfc8040.RestconfModule}.
+ *
+ * @return {@link Module}
+ */
+ public Module getRestconfModule() {
+ return this.findModuleByNamespaceAndRevision(Rfc8040.RestconfModule.IETF_RESTCONF_QNAME.getNamespace(),
+ Rfc8040.RestconfModule.IETF_RESTCONF_QNAME.getRevision());
+ }
+
+ /**
+ * Find {@link Module} in {@link SchemaContext} by {@link URI} and
+ * {@link Date}.
+ *
+ * @param namespace
+ * namespace of module
+ * @param revision
+ * revision of module
+ * @return {@link Module}
+ */
+ public Module findModuleByNamespaceAndRevision(final URI namespace, final Date revision) {
+ return this.get().findModuleByNamespaceAndRevision(namespace, revision);
+ }
+
+
+ /**
+ * Find {@link Module} in {@link SchemaContext} of {@link DOMMountPoint} by
+ * {@link QName} of {@link Module}.
+ *
+ * @param mountPoint
+ * mount point
+ * @param moduleQname
+ * {@link QName} of module
+ * @return {@link Module}
+ */
+ public Module findModuleInMountPointByQName(final DOMMountPoint mountPoint, final QName moduleQname) {
+ final SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext();
+ return schemaContext == null ? null
+ : schemaContext.findModuleByName(moduleQname.getLocalName(), moduleQname.getRevision());
+ }
+
+ /**
+ * Find {@link Module} in {@link SchemaContext} by {@link QName}.
+ *
+ * @param moduleQname
+ * {@link QName} of module
+ * @return {@link Module}
+ */
+ public Module findModuleByQName(final QName moduleQname) {
+ return this.findModuleByNameAndRevision(moduleQname.getLocalName(), moduleQname.getRevision());
+ }
+
+ /**
+ * Find {@link Module} in {@link SchemaContext} by {@link String} localName
+ * and {@link Date} revision.
+ *
+ * @param localName
+ * local name of module
+ * @param revision
+ * revision of module
+ * @return {@link Module}
+ */
+ public Module findModuleByNameAndRevision(final String localName, final Date revision) {
+ return this.get().findModuleByName(localName, revision);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.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.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.Patch;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+
+/**
+ * The "{+restconf}/data" subtree represents the datastore resource type, which
+ * is a collection of configuration data and state data nodes.
+ *
+ */
+public interface RestconfDataService {
+
+ /**
+ * Get target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ */
+ @GET
+ @Path("/data/{identifier:.+}")
+ @Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response readData(@Encoded @PathParam("identifier") String identifier, @Context UriInfo uriInfo);
+
+ /**
+ * Get target data resource from data root.
+ *
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ */
+ @GET
+ @Path("/data")
+ @Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response readData(@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}
+ */
+ @PUT
+ @Path("/data/{identifier:.+}")
+ @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response putData(@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}
+ */
+ @POST
+ @Path("/data/{identifier:.+}")
+ @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response postData(@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}
+ */
+ @POST
+ @Path("/data")
+ @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response postData(NormalizedNodeContext payload, @Context UriInfo uriInfo);
+
+ /**
+ * Delete the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @return {@link Response}
+ */
+ @DELETE
+ @Path("/data/{identifier:.+}")
+ Response deleteData(@Encoded @PathParam("identifier") String identifier);
+
+ /**
+ * 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}
+ */
+ @Patch
+ @Path("/data/{identifier:.+}")
+ @Consumes({ Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON, Rfc8040.MediaTypes.PATCH + RestconfConstants.XML })
+ @Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+ PatchStatusContext patchData(@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}
+ */
+ @Patch
+ @Path("/data")
+ @Consumes({ Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON, Rfc8040.MediaTypes.PATCH + RestconfConstants.XML })
+ @Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+ PatchStatusContext patchData(PatchContext context, @Context UriInfo uriInfo);
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Encoded;
+import javax.ws.rs.POST;
+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.UriInfo;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+
+/**
+ * An operation resource represents a protocol operation defined with the YANG
+ * "rpc" statement. It is invoked using a POST method on the operation resource.
+ *
+ */
+public interface RestconfInvokeOperationsService {
+
+ /**
+ * 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}
+ */
+ @POST
+ @Path("/operations/{identifier:.+}")
+ @Produces({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ NormalizedNodeContext invokeRpc(@Encoded @PathParam("identifier") String identifier,
+ NormalizedNodeContext payload, @Context UriInfo uriInfo);
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.api;
+
+import javax.ws.rs.Encoded;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+
+/**
+ * Subscribing to streams.
+ *
+ */
+public interface RestconfStreamsSubscriptionService {
+
+ /**
+ * Subscribing to receive notification from stream support.
+ *
+ * @param identifier
+ * name of stream
+ * @param uriInfo
+ * URI info
+ * @return {@link NormalizedNodeContext}
+ */
+ @GET
+ @Path("data/ietf-restconf-monitoring:restconf-state/streams/stream/{identifier:.+}")
+ NormalizedNodeContext subscribeToStream(@Encoded @PathParam("identifier") String identifier,
+ @Context UriInfo uriInfo);
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.api;
+
+/**
+ * Wrapper for all transaction services.
+ * <ul>
+ * <li>{@link RestconfDataService}
+ * <li>{@link RestconfInvokeOperationsService}
+ * <li>{@link RestconfStreamsSubscriptionService}
+ * </ul>
+ *
+ */
+public interface TransactionServicesWrapper
+ extends RestconfDataService, RestconfInvokeOperationsService, RestconfStreamsSubscriptionService {
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.impl;
+
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_ACCESS_PATH_PART;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_LOCATION_PATH_PART;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map.Entry;
+import javax.annotation.Nonnull;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.context.WriterParameters;
+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.nb.rfc8040.RestConnectorProvider;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfDataService;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.PutDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link RestconfDataService}.
+ */
+public class RestconfDataServiceImpl implements RestconfDataService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
+
+ private final SchemaContextHandler schemaContextHandler;
+ private final TransactionChainHandler transactionChainHandler;
+ private final DOMMountPointServiceHandler mountPointServiceHandler;
+
+ private final RestconfStreamsSubscriptionService delegRestconfSubscrService;
+
+ public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler,
+ final TransactionChainHandler transactionChainHandler,
+ final DOMMountPointServiceHandler mountPointServiceHandler,
+ final RestconfStreamsSubscriptionService delegRestconfSubscrService) {
+ this.schemaContextHandler = schemaContextHandler;
+ this.transactionChainHandler = transactionChainHandler;
+ this.mountPointServiceHandler = mountPointServiceHandler;
+ this.delegRestconfSubscrService = delegRestconfSubscrService;
+ }
+
+ @Override
+ public Response readData(final UriInfo uriInfo) {
+ return readData(null, uriInfo);
+ }
+
+ @Override
+ public Response readData(final String identifier, final UriInfo uriInfo) {
+ final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
+ final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+ identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
+
+ 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;
+ }
+ }
+ boolean tagged = false;
+ if (withDefaUsed) {
+ if ("report-all-tagged".equals(withDefa)) {
+ tagged = true;
+ withDefa = null;
+ }
+ if ("report-all".equals(withDefa)) {
+ withDefa = null;
+ }
+ }
+
+ final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(
+ instanceIdentifier, uriInfo, tagged);
+
+ final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
+ final DOMTransactionChain transactionChain;
+ if (mountPoint == null) {
+ transactionChain = this.transactionChainHandler.get();
+ } else {
+ transactionChain = transactionChainOfMountPoint(mountPoint);
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ instanceIdentifier, mountPoint, transactionChain);
+ final NormalizedNode<?, ?> node =
+ ReadDataTransactionUtil.readData(identifier, parameters.getContent(), transactionNode, withDefa,
+ schemaContextRef, uriInfo);
+ if (identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART)
+ && identifier.contains(STREAM_LOCATION_PATH_PART)) {
+ final String value = (String) node.getValue();
+ final String streamName = value.substring(
+ value.indexOf(CREATE_NOTIFICATION_STREAM.toString() + RestconfConstants.SLASH),
+ value.length());
+ this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo);
+ }
+ if (node == null) {
+ throw new RestconfDocumentedException(
+ "Request could not be completed because the relevant data model content does not exist",
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.DATA_MISSING);
+ }
+
+ if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL))
+ || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
+ return Response.status(200)
+ .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
+ .header("ETag", '"' + node.getNodeType().getModule().getFormattedRevision()
+ + node.getNodeType().getLocalName() + '"')
+ .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
+ .build();
+ }
+
+ return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build();
+ }
+
+ @Override
+ public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ Preconditions.checkNotNull(payload);
+
+ 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());
+ }
+ }
+
+ checkQueryParams(insertUsed, pointUsed, insert);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iid = payload
+ .getInstanceIdentifierContext();
+
+ PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
+ PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
+ PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
+
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final DOMTransactionChain transactionChain;
+ final SchemaContextRef ref;
+ if (mountPoint == null) {
+ transactionChain = this.transactionChainHandler.get();
+ ref = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ transactionChain = transactionChainOfMountPoint(mountPoint);
+ ref = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
+ return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point);
+ }
+
+ private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) {
+ 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.");
+ }
+ }
+
+ @Override
+ public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return postData(payload, uriInfo);
+ }
+
+ @Override
+ public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ Preconditions.checkNotNull(payload);
+
+ 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());
+ }
+ }
+
+ checkQueryParams(insertUsed, pointUsed, insert);
+
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final DOMTransactionChain transactionChain;
+ final SchemaContextRef ref;
+ if (mountPoint == null) {
+ transactionChain = this.transactionChainHandler.get();
+ ref = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ transactionChain = transactionChainOfMountPoint(mountPoint);
+ ref = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
+ return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref, insert, point);
+ }
+
+ @Override
+ public Response deleteData(final String identifier) {
+ final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
+ final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+ identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
+
+ final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
+ final DOMTransactionChain transactionChain;
+ if (mountPoint == null) {
+ transactionChain = this.transactionChainHandler.get();
+ } else {
+ transactionChain = transactionChainOfMountPoint(mountPoint);
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
+ transactionChain);
+ return DeleteDataTransactionUtil.deleteData(transactionNode);
+ }
+
+ @Override
+ public PatchStatusContext patchData(final String identifier, final PatchContext context, final UriInfo uriInfo) {
+ return patchData(context, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) {
+ Preconditions.checkNotNull(context);
+ final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint();
+
+ final DOMTransactionChain transactionChain;
+ final SchemaContextRef ref;
+ if (mountPoint == null) {
+ transactionChain = this.transactionChainHandler.get();
+ ref = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ transactionChain = transactionChainOfMountPoint(mountPoint);
+ ref = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ context.getInstanceIdentifierContext(), mountPoint, transactionChain);
+
+ return PatchDataTransactionUtil.patchData(context, transactionNode, ref);
+ }
+
+ /**
+ * Prepare transaction chain to access data of mount point.
+ * @param mountPoint
+ * mount point reference
+ * @return {@link DOMTransactionChain}
+ */
+ private static DOMTransactionChain transactionChainOfMountPoint(@Nonnull final DOMMountPoint mountPoint) {
+ final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
+ if (domDataBrokerService.isPresent()) {
+ return domDataBrokerService.get().createTransactionChain(RestConnectorProvider.TRANSACTION_CHAIN_LISTENER);
+ }
+
+ final String errMsg = "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier();
+ LOG.warn(errMsg);
+ throw new RestconfDocumentedException(errMsg);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.impl;
+
+import java.net.URI;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.handlers.RpcServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfInvokeOperationsService;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.CreateStreamUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfInvokeOperationsUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Implementation of {@link RestconfInvokeOperationsService}.
+ *
+ */
+public class RestconfInvokeOperationsServiceImpl implements RestconfInvokeOperationsService {
+
+ private final RpcServiceHandler rpcServiceHandler;
+ private final SchemaContextHandler schemaContextHandler;
+
+ public RestconfInvokeOperationsServiceImpl(final RpcServiceHandler rpcServiceHandler,
+ final SchemaContextHandler schemaContextHandler) {
+ this.rpcServiceHandler = rpcServiceHandler;
+ this.schemaContextHandler = schemaContextHandler;
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ final SchemaContextRef refSchemaCtx = new SchemaContextRef(this.schemaContextHandler.get());
+ final SchemaPath schemaPath = payload.getInstanceIdentifierContext().getSchemaNode().getPath();
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final URI namespace = payload.getInstanceIdentifierContext().getSchemaNode().getQName().getNamespace();
+ DOMRpcResult response;
+
+ SchemaContextRef schemaContextRef;
+
+ if (mountPoint == null) {
+ if (namespace.toString().equals(RestconfStreamsConstants.SAL_REMOTE_NAMESPACE)) {
+ if (identifier.contains(RestconfStreamsConstants.CREATE_DATA_SUBSCR)) {
+ response = CreateStreamUtil.createDataChangeNotifiStream(payload, refSchemaCtx);
+ } else {
+ throw new RestconfDocumentedException("Not supported operation", ErrorType.RPC,
+ ErrorTag.OPERATION_NOT_SUPPORTED);
+ }
+ } else {
+ response = RestconfInvokeOperationsUtil.invokeRpc(payload.getData(), schemaPath,
+ this.rpcServiceHandler);
+ }
+ schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ response = RestconfInvokeOperationsUtil.invokeRpcViaMountPoint(mountPoint, payload.getData(), schemaPath);
+ schemaContextRef = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+
+ final DOMRpcResult result = RestconfInvokeOperationsUtil.checkResponse(response);
+
+ RpcDefinition resultNodeSchema = null;
+ final NormalizedNode<?, ?> resultData = result.getResult();
+ if ((result != null) && (result.getResult() != null)) {
+ resultNodeSchema = (RpcDefinition) payload.getInstanceIdentifierContext().getSchemaNode();
+ }
+ return new NormalizedNodeContext(new InstanceIdentifierContext<RpcDefinition>(null, resultNodeSchema,
+ mountPoint, schemaContextRef.get()), resultData);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.services.impl;
+
+import java.net.URI;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import javax.annotation.Nonnull;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMDataBrokerHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.NotificationServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.SubscribeToStreamUtil;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link RestconfStreamsSubscriptionService}.
+ *
+ */
+public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSubscriptionService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfStreamsSubscriptionServiceImpl.class);
+
+ private final HandlersHolder handlersHolder;
+
+ /**
+ * Initialize holder of handlers with holders as parameters.
+ *
+ * @param domDataBrokerHandler
+ * handler of {@link DOMDataBroker}
+ * @param notificationServiceHandler
+ * handler of {@link DOMNotificationService}
+ * @param schemaHandler
+ * handler of {@link SchemaContext}
+ * @param transactionChainHandler
+ * handler of {@link DOMTransactionChain}
+ */
+ public RestconfStreamsSubscriptionServiceImpl(final DOMDataBrokerHandler domDataBrokerHandler,
+ final NotificationServiceHandler notificationServiceHandler, final SchemaContextHandler schemaHandler,
+ final TransactionChainHandler transactionChainHandler) {
+ this.handlersHolder = new HandlersHolder(domDataBrokerHandler, notificationServiceHandler,
+ transactionChainHandler, schemaHandler);
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ final NotificationQueryParams notificationQueryParams = NotificationQueryParams.fromUriInfo(uriInfo);
+
+ URI response = null;
+ if (identifier.contains(RestconfStreamsConstants.DATA_SUBSCR)) {
+ response = SubscribeToStreamUtil.notifiDataStream(identifier, uriInfo, notificationQueryParams,
+ this.handlersHolder);
+ } else if (identifier.contains(RestconfStreamsConstants.NOTIFICATION_STREAM)) {
+ response = SubscribeToStreamUtil.notifYangStream(identifier, uriInfo, notificationQueryParams,
+ this.handlersHolder);
+ }
+
+ if (response != null) {
+ // prepare node with value of location
+ final InstanceIdentifierContext<?> iid =
+ SubscribeToStreamUtil.prepareIIDSubsStreamOutput(this.handlersHolder.getSchemaHandler());
+ final NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> builder =
+ ImmutableLeafNodeBuilder.create().withValue(response.toString());
+ builder.withNodeIdentifier(
+ NodeIdentifier.create(QName.create("subscribe:to:notification", "2016-10-28", "location")));
+
+ // prepare new header with location
+ final Map<String, Object> headers = new HashMap<>();
+ headers.put("Location", response);
+
+ return new NormalizedNodeContext(iid, builder.build(), headers);
+ }
+
+ final String msg = "Bad type of notification of sal-remote";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg);
+ }
+
+ /**
+ * Holder of all handlers for notifications.
+ */
+ public final class HandlersHolder {
+
+ private final DOMDataBrokerHandler domDataBrokerHandler;
+ private final NotificationServiceHandler notificationServiceHandler;
+ private final TransactionChainHandler transactionChainHandler;
+ private final SchemaContextHandler schemaHandler;
+
+ private HandlersHolder(final DOMDataBrokerHandler domDataBrokerHandler,
+ final NotificationServiceHandler notificationServiceHandler,
+ final TransactionChainHandler transactionChainHandler, final SchemaContextHandler schemaHandler) {
+ this.domDataBrokerHandler = domDataBrokerHandler;
+ this.notificationServiceHandler = notificationServiceHandler;
+ this.transactionChainHandler = transactionChainHandler;
+ this.schemaHandler = schemaHandler;
+ }
+
+ /**
+ * Get {@link DOMDataBrokerHandler}.
+ *
+ * @return the domDataBrokerHandler
+ */
+ public DOMDataBrokerHandler getDomDataBrokerHandler() {
+ return this.domDataBrokerHandler;
+ }
+
+ /**
+ * Get {@link NotificationServiceHandler}.
+ *
+ * @return the notificationServiceHandler
+ */
+ public NotificationServiceHandler getNotificationServiceHandler() {
+ return this.notificationServiceHandler;
+ }
+
+ /**
+ * Get {@link TransactionChainHandler}.
+ *
+ * @return the transactionChainHandler
+ */
+ public TransactionChainHandler getTransactionChainHandler() {
+ return this.transactionChainHandler;
+ }
+
+ /**
+ * Get {@link SchemaContextHandler}.
+ *
+ * @return the schemaHandler
+ */
+ public SchemaContextHandler getSchemaHandler() {
+ return this.schemaHandler;
+ }
+ }
+
+ /**
+ * Parser and holder of query paramteres from uriInfo for notifications.
+ *
+ */
+ public static final class NotificationQueryParams {
+
+ private final Instant start;
+ private final Instant stop;
+ private final String filter;
+
+ private NotificationQueryParams(final Instant start, final Instant stop, final String filter) {
+ this.start = start == null ? Instant.now() : start;
+ this.stop = stop;
+ this.filter = filter;
+ }
+
+ static NotificationQueryParams fromUriInfo(final UriInfo uriInfo) {
+ Instant start = null;
+ boolean startTimeUsed = false;
+ Instant stop = null;
+ boolean stopTimeUsed = false;
+ String filter = null;
+ boolean filterUsed = false;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "start-time":
+ if (!startTimeUsed) {
+ startTimeUsed = true;
+ start = SubscribeToStreamUtil.parseDateFromQueryParam(entry);
+ } else {
+ throw new RestconfDocumentedException("Start-time parameter can be used only once.");
+ }
+ break;
+ case "stop-time":
+ if (!stopTimeUsed) {
+ stopTimeUsed = true;
+ stop = SubscribeToStreamUtil.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();
+ }
+ 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.");
+ }
+
+ return new NotificationQueryParams(start, stop, filter);
+ }
+
+ /**
+ * Get start-time query parameter.
+ *
+ * @return start-time
+ */
+ @Nonnull
+ public Instant getStart() {
+ return start;
+ }
+
+ /**
+ * Get stop-time query parameter.
+ *
+ * @return stop-time
+ */
+ public Optional<Instant> getStop() {
+ return Optional.ofNullable(stop);
+ }
+
+ /**
+ * Get filter query parameter.
+ *
+ * @return filter
+ */
+ public Optional<String> getFilter() {
+ return Optional.ofNullable(filter);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.transactions;
+
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+
+/**
+ * This class represent delegation wrapper for transaction variables.
+ *
+ */
+public final class TransactionVarsWrapper {
+
+ private final InstanceIdentifierContext<?> instanceIdentifier;
+ private final DOMMountPoint mountPoint;
+ private LogicalDatastoreType configuration = null;
+ private final DOMTransactionChain transactionChain;
+
+ /**
+ * Set base type of variables, which ones we need for transaction.
+ * {@link LogicalDatastoreType} is default set to null (to read all data
+ * from DS - config + state).
+ *
+ * @param instanceIdentifier
+ * {@link InstanceIdentifierContext} of data for transaction
+ * @param mountPoint
+ * mount point if is present
+ * @param transactionChain
+ * transaction chain for creating specific type of transaction
+ * in specific operation
+ */
+ public TransactionVarsWrapper(final InstanceIdentifierContext<?> instanceIdentifier, final DOMMountPoint mountPoint,
+ final DOMTransactionChain transactionChain) {
+ this.instanceIdentifier = instanceIdentifier;
+ this.mountPoint = mountPoint;
+ this.transactionChain = transactionChain;
+ }
+
+ /**
+ * Get instance identifier of data.
+ *
+ * @return {@link InstanceIdentifierContext}
+ */
+ public InstanceIdentifierContext<?> getInstanceIdentifier() {
+ return this.instanceIdentifier;
+ }
+
+ /**
+ * Get mount point.
+ *
+ * @return {@link DOMMountPoint}
+ */
+ public DOMMountPoint getMountPoint() {
+ return this.mountPoint;
+ }
+
+ /**
+ * Set {@link LogicalDatastoreType} of data for transaction.
+ *
+ * @param configuration
+ * {@link LogicalDatastoreType}
+ */
+ public void setLogicalDatastoreType(final LogicalDatastoreType configuration) {
+ this.configuration = configuration;
+
+ }
+
+ /**
+ * Get type of data.
+ *
+ * @return {@link LogicalDatastoreType}
+ */
+ public LogicalDatastoreType getLogicalDatastoreType() {
+ return this.configuration;
+ }
+
+ /**
+ * Get transaction chain for creating specific transaction for specific
+ * operation.
+ *
+ * @return transaction chain
+ */
+ public DOMTransactionChain getTransactionChain() {
+ return this.transactionChain;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
+import java.util.List;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.Notificator;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+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.NodeIdentifier;
+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.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util class for streams.
+ *
+ * <ul>
+ * <li>create stream
+ * <li>subscribe
+ * </ul>
+ *
+ */
+public final class CreateStreamUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
+ private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
+
+ private CreateStreamUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Create stream with POST operation via RPC.
+ *
+ * @param payload
+ * input of rpc - example in JSON:
+ *
+ * <pre>
+ * {@code
+ * {
+ * "input": {
+ * "path": "/toaster:toaster/toaster:toasterStatus",
+ * "sal-remote-augment:datastore": "OPERATIONAL",
+ * "sal-remote-augment:scope": "ONE"
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @param refSchemaCtx
+ * reference to {@link SchemaContext} -
+ * {@link SchemaContextRef}
+ * @return {@link CheckedFuture} with {@link DOMRpcResult} - This mean
+ * output of RPC - example in JSON:
+ *
+ * <pre>
+ * {@code
+ * {
+ * "output": {
+ * "stream-name": "toaster:toaster/toaster:toasterStatus/datastore=OPERATIONAL/scope=ONE"
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ */
+ public static DOMRpcResult createDataChangeNotifiStream(final NormalizedNodeContext payload,
+ final SchemaContextRef refSchemaCtx) {
+ final ContainerNode data = (ContainerNode) payload.getData();
+ final QName qname = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+ final YangInstanceIdentifier path = preparePath(data, qname);
+ String streamName = prepareDataChangeNotifiStreamName(path, refSchemaCtx.get(), data);
+
+ final QName outputQname = QName.create(qname, "output");
+ final QName streamNameQname = QName.create(qname, "stream-name");
+
+ final NotificationOutputType outputType = prepareOutputType(data);
+ if (outputType.equals(NotificationOutputType.JSON)) {
+ streamName = streamName + "/JSON";
+ }
+
+ if (!Notificator.existListenerFor(streamName)) {
+ Notificator.createListener(path, streamName, outputType);
+ }
+
+ final ContainerNode output =
+ ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
+ .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
+ return new DefaultDOMRpcResult(output);
+ }
+
+ /**
+ * Prepare {@code NotificationOutputType}.
+ *
+ * @param data
+ * data of notification
+ * @return output type fo notification
+ */
+ private static NotificationOutputType prepareOutputType(final ContainerNode data) {
+ NotificationOutputType outputType = parseEnum(data, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
+ return outputType = outputType == null ? NotificationOutputType.XML : outputType;
+ }
+
+ private static String prepareDataChangeNotifiStreamName(final YangInstanceIdentifier path,
+ final SchemaContext schemaContext,
+ final ContainerNode data) {
+ LogicalDatastoreType ds = parseEnum(data, LogicalDatastoreType.class,
+ RestconfStreamsConstants.DATASTORE_PARAM_NAME);
+ ds = ds == null ? RestconfStreamsConstants.DEFAULT_DS : ds;
+
+ DataChangeScope scope = parseEnum(data, DataChangeScope.class, RestconfStreamsConstants.SCOPE_PARAM_NAME);
+ scope = scope == null ? RestconfStreamsConstants.DEFAULT_SCOPE : scope;
+
+ final String streamName = RestconfStreamsConstants.DATA_SUBSCR + "/"
+ + Notificator
+ .createStreamNameFromUri(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext)
+ + RestconfStreamsConstants.DS_URI + ds + RestconfStreamsConstants.SCOPE_URI + scope);
+ return streamName;
+ }
+
+ private static <T> T parseEnum(final ContainerNode data, final Class<T> clazz, final String paramName) {
+ final Optional<DataContainerChild<? extends PathArgument, ?>> augNode = data
+ .getChild(RestconfStreamsConstants.SAL_REMOTE_AUG_IDENTIFIER);
+ if (!augNode.isPresent() && !(augNode instanceof AugmentationNode)) {
+ return null;
+ }
+ final Optional<DataContainerChild<? extends PathArgument, ?>> enumNode =
+ ((AugmentationNode) augNode.get()).getChild(
+ new NodeIdentifier(QName.create(RestconfStreamsConstants.SAL_REMOTE_AUGMENT, paramName)));
+ if (!enumNode.isPresent()) {
+ return null;
+ }
+ final Object value = enumNode.get().getValue();
+ if (!(value instanceof String)) {
+ return null;
+ }
+
+ return ResolveEnumUtil.resolveEnum(clazz, (String) value);
+ }
+
+ private static YangInstanceIdentifier preparePath(final ContainerNode data, final QName qualifiedName) {
+ final Optional<DataContainerChild<? extends PathArgument, ?>> path = data
+ .getChild(new YangInstanceIdentifier.NodeIdentifier(QName.create(qualifiedName, "path")));
+ Object pathValue = null;
+ if (path.isPresent()) {
+ pathValue = path.get().getValue();
+ }
+ if (!(pathValue instanceof YangInstanceIdentifier)) {
+ final String errMsg = "Instance identifier was not normalized correctly ";
+ LOG.debug(errMsg + qualifiedName);
+ throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+ return (YangInstanceIdentifier) pathValue;
+ }
+
+ /**
+ * Create stream with POST operation via RPC.
+ *
+ * @param notificatinoDefinition
+ * input of RPC
+ * @param refSchemaCtx
+ * schemaContext
+ * @param outputType
+ * output type
+ * @return {@link DOMRpcResult}
+ */
+ public static List<NotificationListenerAdapter> createYangNotifiStream(
+ final NotificationDefinition notificatinoDefinition, final SchemaContextRef refSchemaCtx,
+ final String outputType) {
+ final List<SchemaPath> paths = new ArrayList<>();
+ final QName notificatinoDefinitionQName = notificatinoDefinition.getQName();
+ final Module module =
+ refSchemaCtx.findModuleByNamespaceAndRevision(notificatinoDefinitionQName.getModule().getNamespace(),
+ notificatinoDefinitionQName.getModule().getRevision());
+ Preconditions.checkNotNull(module,
+ "Module for namespace " + notificatinoDefinitionQName.getModule().getNamespace() + " does not exist");
+ NotificationDefinition notifiDef = null;
+ for (final NotificationDefinition notification : module.getNotifications()) {
+ if (notification.getQName().equals(notificatinoDefinitionQName)) {
+ notifiDef = notification;
+ break;
+ }
+ }
+ final String moduleName = module.getName();
+ Preconditions.checkNotNull(notifiDef,
+ "Notification " + notificatinoDefinitionQName + "doesn't exist in module " + moduleName);
+ paths.add(notifiDef.getPath());
+ String streamName = RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM + "/";
+ streamName = streamName + moduleName + ":" + notificatinoDefinitionQName.getLocalName();
+ if (outputType.equals("JSON")) {
+ streamName = streamName + "/JSON";
+ }
+
+ if (Notificator.existNotificationListenerFor(streamName)) {
+ final List<NotificationListenerAdapter> notificationListenerFor =
+ Notificator.getNotificationListenerFor(streamName);
+ return SubscribeToStreamUtil.pickSpecificListenerByOutput(notificationListenerFor, outputType);
+ }
+
+ return Notificator.createNotificationListener(paths, streamName, outputType);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import javax.ws.rs.core.Response;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * Util class for delete specific data in config DS.
+ *
+ */
+public final class DeleteDataTransactionUtil {
+
+ private DeleteDataTransactionUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Delete data from DS via transaction.
+ *
+ * @param transactionNode
+ * Wrapper for data of transaction
+ * @return {@link Response}
+ */
+ public static Response deleteData(final TransactionVarsWrapper transactionNode) {
+ final CheckedFuture<Void, TransactionCommitFailedException> future = submitData(
+ transactionNode.getTransactionChain(), transactionNode.getTransactionChain().newReadWriteTransaction(),
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier());
+ final ResponseFactory response = new ResponseFactory();
+ FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE, response);
+ return response.build();
+ }
+
+ /**
+ * Delete data via transaction. Return error if data to delete does not exist.
+ *
+ * @param transactionChain
+ * transaction chain
+ * @param readWriteTx
+ * read and write transaction
+ * @param path
+ * path of data to delete
+ * @return {@link CheckedFuture}
+ */
+ private static CheckedFuture<Void, TransactionCommitFailedException> submitData(
+ final DOMTransactionChain transactionChain, final DOMDataReadWriteTransaction readWriteTx,
+ final YangInstanceIdentifier path) {
+ TransactionUtil.checkItemExists(transactionChain, readWriteTx, LogicalDatastoreType.CONFIGURATION, path,
+ RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE);
+ readWriteTx.delete(LogicalDatastoreType.CONFIGURATION, path);
+ return readWriteTx.submit();
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
+import java.util.List;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Add callback for future objects and result set to the data factory.
+ *
+ */
+final class FutureCallbackTx {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FutureCallbackTx.class);
+
+ private FutureCallbackTx() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Add callback to the future object.
+ *
+ * @param listenableFuture
+ * future object
+ * @param txType
+ * type of operation (READ, POST, PUT, DELETE)
+ * @param dataFactory
+ * factory setting result
+ * @throws RestconfDocumentedException
+ * if the Future throws an exception
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ static <T, X extends Exception> void addCallback(final CheckedFuture<T, X> listenableFuture, final String txType,
+ final FutureDataFactory<T> dataFactory) throws RestconfDocumentedException {
+
+ try {
+ final T result = listenableFuture.checkedGet();
+ dataFactory.setResult(result);
+ LOG.trace("Transaction({}) SUCCESSFUL", txType);
+ } catch (Exception e) {
+ dataFactory.setFailureStatus();
+ LOG.warn("Transaction({}) FAILED!", txType, e);
+ if (e instanceof DOMRpcException) {
+ final List<RpcError> rpcErrorList = new ArrayList<>();
+ rpcErrorList.add(
+ RpcResultBuilder.newError(RpcError.ErrorType.RPC, "operation-failed", e.getMessage()));
+ dataFactory.setResult((T) new DefaultDOMRpcResult(rpcErrorList));
+ } else {
+ throw new RestconfDocumentedException(
+ "Transaction(" + txType + ") not committed correctly", e);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+class FutureDataFactory<T> {
+
+ protected T result;
+ private boolean statusFail = false;
+
+ void setResult(final T result) {
+ this.result = result;
+ }
+
+ void setFailureStatus() {
+ this.statusFail = true;
+ }
+
+ boolean getFailureStatus() {
+ return statusFail;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Optional;
+import org.apache.commons.lang3.builder.Builder;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class NormalizedNodeFactory extends FutureDataFactory<Optional<NormalizedNode<?, ?>>>
+ implements Builder<NormalizedNode<?, ?>> {
+
+ @Override
+ public NormalizedNode<?, ?> build() {
+ if (this.result.isPresent()) {
+ return this.result.get();
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+
+class ParametersUtil {
+
+ private ParametersUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Check if URI does not contain not allowed parameters for specified operation.
+ *
+ * @param operationType
+ * type of operation (READ, POST, PUT, DELETE...)
+ * @param usedParameters
+ * parameters used in URI request
+ * @param allowedParameters
+ * allowed parameters for operation
+ */
+ static void checkParametersTypes(@Nonnull final String operationType,
+ @Nonnull final Set<String> usedParameters,
+ @Nonnull final String... allowedParameters) {
+ final Set<String> notAllowedParameters = Sets.newHashSet(usedParameters);
+ notAllowedParameters.removeAll(Sets.newHashSet(allowedParameters));
+
+ if (!notAllowedParameters.isEmpty()) {
+ throw new RestconfDocumentedException(
+ "Not allowed parameters for " + operationType + " operation: " + notAllowedParameters,
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.INVALID_VALUE);
+ }
+ }
+
+ /**
+ * Check if URI does not contain value for the same parameter more than once.
+ *
+ * @param parameterValues
+ * URI parameter values
+ * @param parameterName
+ * URI parameter name
+ */
+ static void checkParameterCount(@Nonnull final List<String> parameterValues, @Nonnull final String parameterName) {
+ if (parameterValues.size() > 1) {
+ throw new RestconfDocumentedException(
+ "Parameter " + parameterName + " can appear at most once in request URI",
+ ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
+import java.util.List;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchEntity;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.patch.PatchStatusEntity;
+import org.opendaylight.restconf.nb.rfc8040.RestConnectorProvider;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PatchData;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class PatchDataTransactionUtil {
+ private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
+
+ private PatchDataTransactionUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Process edit operations of one {@link PatchContext}.
+ * @param context Patch context to be processed
+ * @param transactionNode Wrapper for transaction
+ * @param schemaContextRef Soft reference for global schema context
+ * @return {@link PatchStatusContext}
+ */
+ public static PatchStatusContext patchData(final PatchContext context, final TransactionVarsWrapper transactionNode,
+ final SchemaContextRef schemaContextRef) {
+ final List<PatchStatusEntity> editCollection = new ArrayList<>();
+ boolean noError = true;
+ final DOMDataReadWriteTransaction tx = transactionNode.getTransactionChain().newReadWriteTransaction();
+
+ for (final PatchEntity patchEntity : context.getData()) {
+ if (noError) {
+ switch (patchEntity.getOperation()) {
+ case CREATE:
+ try {
+ createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+ patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(e.getErrors())));
+ noError = false;
+ }
+ break;
+ case DELETE:
+ try {
+ deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
+ tx);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(e.getErrors())));
+ noError = false;
+ }
+ break;
+ case MERGE:
+ try {
+ mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+ patchEntity.getTargetNode(), patchEntity.getNode(), tx, schemaContextRef);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(e.getErrors())));
+ noError = false;
+ }
+ break;
+ case REPLACE:
+ try {
+ replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+ patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef, tx);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(e.getErrors())));
+ noError = false;
+ }
+ break;
+ case REMOVE:
+ try {
+ removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION, patchEntity.getTargetNode(),
+ tx);
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (final RestconfDocumentedException e) {
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(e.getErrors())));
+ noError = false;
+ }
+ break;
+ default:
+ editCollection.add(new PatchStatusEntity(patchEntity.getEditId(),
+ false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang Patch operation"))));
+ noError = false;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // if no errors then submit transaction, otherwise cancel
+ if (noError) {
+ final ResponseFactory response = new ResponseFactory();
+ final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
+
+ try {
+ FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
+ } catch (final RestconfDocumentedException e) {
+ // if errors occurred during transaction commit then patch failed and global errors are reported
+ return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
+ Lists.newArrayList(e.getErrors()));
+ }
+
+ return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
+ } else {
+ tx.cancel();
+ RestConnectorProvider.resetTransactionChainForAdapaters(transactionNode.getTransactionChain());
+ return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
+ false, null);
+ }
+ }
+
+ /**
+ * Create data within one transaction, return error if already exists.
+ * @param dataStore Datastore to write data to
+ * @param path Path for data to be created
+ * @param payload Data to be created
+ * @param rWTransaction Transaction
+ * @param schemaContextRef Soft reference for global schema context
+ */
+ private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> payload,
+ final DOMDataReadWriteTransaction rwTransaction,
+ final SchemaContextRef schemaContextRef) {
+ LOG.trace("POST {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
+ createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, true);
+ }
+
+ /**
+ * Check if data exists and remove it within one transaction.
+ * @param dataStore Datastore to delete data from
+ * @param path Path for data to be deleted
+ * @param readWriteTransaction Transaction
+ */
+ private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final DOMDataReadWriteTransaction readWriteTransaction) {
+ LOG.trace("Delete {} within Restconf Patch: {}", dataStore.name(), path);
+ checkItemExistsWithinTransaction(readWriteTransaction, dataStore, path);
+ readWriteTransaction.delete(dataStore, path);
+ }
+
+ /**
+ * Merge data within one transaction.
+ * @param dataStore Datastore to merge data to
+ * @param path Path for data to be merged
+ * @param payload Data to be merged
+ * @param writeTransaction Transaction
+ * @param schemaContextRef Soft reference for global schema context
+ */
+ private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> payload,
+ final DOMDataReadWriteTransaction writeTransaction,
+ final SchemaContextRef schemaContextRef) {
+ LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
+ TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
+ writeTransaction.merge(dataStore, path, payload);
+ }
+
+ /**
+ * Do NOT check if data exists and remove it within one transaction.
+ * @param dataStore Datastore to delete data from
+ * @param path Path for data to be deleted
+ * @param writeTransaction Transaction
+ */
+ private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final DOMDataWriteTransaction writeTransaction) {
+ LOG.trace("Remove {} within Restconf Patch: {}", dataStore.name(), path);
+ writeTransaction.delete(dataStore, path);
+ }
+
+ /**
+ * Create data within one transaction, replace if already exists.
+ * @param dataStore Datastore to write data to
+ * @param path Path for data to be created
+ * @param payload Data to be created
+ * @param schemaContextRef Soft reference for global schema context
+ * @param rwTransaction Transaction
+ */
+ private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> payload,
+ final SchemaContextRef schemaContextRef,
+ final DOMDataReadWriteTransaction rwTransaction) {
+ LOG.trace("PUT {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
+ createData(payload, schemaContextRef.get(), path, rwTransaction, dataStore, false);
+ }
+
+ /**
+ * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
+ * for existence before created, otherwise they will be overwritten.
+ * @param payload Data to be created
+ * @param schemaContext Global schema context
+ * @param path Path for data to be created
+ * @param rwTransaction Transaction
+ * @param dataStore Datastore to write data to
+ * @param errorIfExists Enable checking for existence of data (throws error if already exists)
+ */
+ private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
+ final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType dataStore, final boolean errorIfExists) {
+ if (payload instanceof MapNode) {
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ rwTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
+ for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+
+ if (errorIfExists) {
+ checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, childPath);
+ }
+
+ rwTransaction.put(dataStore, childPath, child);
+ }
+ } else {
+ if (errorIfExists) {
+ checkItemDoesNotExistsWithinTransaction(rwTransaction, dataStore, path);
+ }
+
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
+ rwTransaction.put(dataStore, path, payload);
+ }
+ }
+
+ /**
+ * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+ * data does NOT already exists.
+ * @param rwTransaction Transaction
+ * @param store Datastore
+ * @param path Path to be checked
+ */
+ public static void checkItemExistsWithinTransaction(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
+ final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+ FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
+
+ if (!response.result) {
+ final String errMsg = "Operation via Restconf was not executed because data does not exist";
+ LOG.trace("{}:{}", errMsg, path);
+ throw new RestconfDocumentedException(
+ "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
+ }
+ }
+
+ /**
+ * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+ * data already exists.
+ * @param rwTransaction Transaction
+ * @param store Datastore
+ * @param path Path to be checked
+ */
+ public static void checkItemDoesNotExistsWithinTransaction(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
+ final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+ FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
+
+ if (response.result) {
+ final String errMsg = "Operation via Restconf was not executed because data already exists";
+ LOG.trace("{}:{}", errMsg, path);
+ throw new RestconfDocumentedException(
+ "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.net.URI;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+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.OrderedLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util class to post data to DS.
+ *
+ */
+public final class PostDataTransactionUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PostDataTransactionUtil.class);
+
+ private PostDataTransactionUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Check mount point and prepare variables for post data.
+ *
+ * @param uriInfo
+ *
+ * @param payload
+ * data
+ * @param transactionNode
+ * wrapper for transaction data
+ * @param schemaContextRef
+ * reference to actual {@link SchemaContext}
+ * @param point
+ * point
+ * @param insert
+ * insert
+ * @return {@link CheckedFuture}
+ */
+ public static Response postData(final UriInfo uriInfo, final NormalizedNodeContext payload,
+ final TransactionVarsWrapper transactionNode, final SchemaContextRef schemaContextRef, final String insert,
+ final String point) {
+ final CheckedFuture<Void, TransactionCommitFailedException> future = submitData(
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
+ transactionNode, schemaContextRef.get(), insert, point);
+ final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef);
+ final ResponseFactory dataFactory = new ResponseFactory(null, location);
+ FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
+ return dataFactory.build();
+ }
+
+ /**
+ * Post data by type.
+ *
+ * @param path
+ * path
+ * @param data
+ * data
+ * @param transactionNode
+ * wrapper for data to transaction
+ * @param schemaContext
+ * schema context of data
+ * @param point
+ * query parameter
+ * @param insert
+ * query parameter
+ * @return {@link CheckedFuture}
+ */
+ private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data, final TransactionVarsWrapper transactionNode,
+ final SchemaContext schemaContext, final String insert, final String point) {
+ final DOMTransactionChain domTransactionChain = transactionNode.getTransactionChain();
+ final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
+ if (insert == null) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+ schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
+ path.getParent().getParent());
+ simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
+ schemaContext, domTransactionChain);
+ makePost(path, readData, schemaContext, domTransactionChain,
+ newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData = PutDataTransactionUtil
+ .readList(path.getParent(), schemaContext, domTransactionChain, schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION,
+ path.getParent().getParent());
+ simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data,
+ schemaContext, domTransactionChain);
+ makePost(path, readData, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ case "last":
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+ schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
+ data, schemaContext, point, readList, true, domTransactionChain);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData =
+ PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+ schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+ path, data, schemaContext, point, readLeafList, true, domTransactionChain);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+ schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
+ data, schemaContext, point, readList, false, domTransactionChain);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData =
+ PutDataTransactionUtil.readList(path.getParent(), schemaContext, domTransactionChain,
+ schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ makePost(path, data, schemaContext, domTransactionChain, newReadWriteTransaction);
+ return newReadWriteTransaction.submit();
+ } else {
+ insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+ path, data, schemaContext, point, readLeafList, true, domTransactionChain);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, "
+ + "but was: " + insert);
+ }
+ }
+ }
+
+ private static void insertWithPointLeafListPost(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+ final SchemaContext schemaContext, final String point, final OrderedLeafSetNode<?> readLeafList,
+ final boolean before, final DOMTransactionChain domTransactionChain) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext<?> instanceIdentifier =
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
+ int lastItemPosition = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
+ 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.getValue()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ TransactionUtil.checkItemDoesNotExists(domTransactionChain, rwTransaction, datastore, path,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ rwTransaction.put(datastore, path, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier());
+ TransactionUtil.checkItemDoesNotExists(domTransactionChain, rwTransaction, datastore, childPath,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ rwTransaction.put(datastore, childPath, nodeChild);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static void insertWithPointListPost(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+ final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before,
+ final DOMTransactionChain domTransactionChain) {
+ rwTransaction.delete(datastore, path.getParent().getParent());
+ final InstanceIdentifierContext<?> instanceIdentifier =
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
+ int lastItemPosition = 0;
+ for (final MapEntryNode mapEntryNode : readList.getValue()) {
+ 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.getValue()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ TransactionUtil.checkItemDoesNotExists(domTransactionChain, rwTransaction, datastore, path,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ rwTransaction.put(datastore, path, payload);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier());
+ TransactionUtil.checkItemDoesNotExists(domTransactionChain, rwTransaction, datastore, childPath,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ rwTransaction.put(datastore, childPath, mapEntryNode);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static void makePost(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
+ final SchemaContext schemaContext, final DOMTransactionChain transactionChain,
+ final DOMDataReadWriteTransaction transaction) {
+ if (data instanceof MapNode) {
+ boolean merge = false;
+ for (final MapEntryNode child : ((MapNode) data).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ TransactionUtil.checkItemDoesNotExists(
+ transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, childPath,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ if (!merge) {
+ merge = true;
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
+ final NormalizedNode<?, ?> emptySubTree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ transaction.merge(LogicalDatastoreType.CONFIGURATION,
+ YangInstanceIdentifier.create(emptySubTree.getIdentifier()), emptySubTree);
+ }
+ transaction.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
+ }
+ } else {
+ TransactionUtil.checkItemDoesNotExists(
+ transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, path,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction);
+ transaction.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ }
+ }
+
+ /**
+ * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}.
+ *
+ * @param uriInfo
+ * uri info
+ * @param transactionNode
+ * wrapper for data of transaction
+ * @param schemaContextRef
+ * reference to {@link SchemaContext}
+ * @return {@link URI}
+ */
+ private static URI resolveLocation(final UriInfo uriInfo, final TransactionVarsWrapper transactionNode,
+ final SchemaContextRef schemaContextRef) {
+ if (uriInfo == null) {
+ return null;
+ }
+
+ final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("data");
+ uriBuilder.path(ParserIdentifier
+ .stringFromYangInstanceIdentifier(transactionNode.getInstanceIdentifier().getInstanceIdentifier(),
+ schemaContextRef.get()));
+
+ return uriBuilder.build();
+ }
+
+ private static void simplePost(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+ final SchemaContext schemaContext, final DOMTransactionChain transactionChain) {
+ TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path,
+ RestconfDataServiceConstant.PostData.POST_TX_TYPE);
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction);
+ rwTransaction.put(datastore, path, payload);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.core.Response;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+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.PathArgument;
+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.OrderedLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+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.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Util class for put data to DS.
+ *
+ */
+public final class PutDataTransactionUtil {
+
+ /**
+ * Valid input data with {@link SchemaNode}.
+ *
+ * @param schemaNode
+ * {@link SchemaNode}
+ * @param payload
+ * input data
+ */
+ public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) {
+ if ((schemaNode != null) && (payload.getData() == null)) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ } else if ((schemaNode == null) && (payload.getData() != null)) {
+ throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+
+ /**
+ * Valid top level node name.
+ *
+ * @param path
+ * path of node
+ * @param payload
+ * data
+ */
+ public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) {
+ final String payloadName = payload.getData().getNodeType().getLocalName();
+
+ if (path.isEmpty()) {
+ if (!payload.getData().getNodeType().equals(RestconfDataServiceConstant.NETCONF_BASE_QNAME)) {
+ throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ } else {
+ final String identifierName = path.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
+ */
+ public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) {
+ 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)
+ .getKeyValues();
+ isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions);
+ }
+ }
+ }
+
+ private static void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final MapEntryNode payload,
+ final List<QName> keyDefinitions) {
+ final Map<QName, Object> mutableCopyUriKeyValues = Maps.newHashMap(uriKeyValues);
+ for (final QName keyDefinition : keyDefinitions) {
+ final Object uriKeyValue = mutableCopyUriKeyValues.remove(keyDefinition);
+ RestconfValidationUtils.checkDocumentedError(uriKeyValue != null, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING,
+ "Missing key " + keyDefinition + " in URI.");
+
+ final Object dataKeyValue = payload.getIdentifier().getKeyValues().get(keyDefinition);
+
+ if (!uriKeyValue.equals(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);
+ }
+ }
+ }
+
+ /**
+ * Check mount point and prepare variables for put data to DS.
+ *
+ * @param payload
+ * data to put
+ * @param schemaCtxRef
+ * reference to {@link SchemaContext}
+ * @param transactionNode
+ * wrapper of variables for transaction
+ * @param point
+ * query parameter
+ * @param insert
+ * query parameter
+ * @return {@link CheckedFuture}
+ */
+ public static Response putData(final NormalizedNodeContext payload, final SchemaContextRef schemaCtxRef,
+ final TransactionVarsWrapper transactionNode, final String insert, final String point) {
+ final YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
+ final SchemaContext schemaContext = schemaCtxRef.get();
+ final ResponseFactory responseFactory = new ResponseFactory(
+ ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode,
+ schemaContext));
+ final CheckedFuture<Void, TransactionCommitFailedException> submitData = submitData(path, schemaContext,
+ transactionNode.getTransactionChain(), payload.getData(), insert, point);
+ FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory);
+ return responseFactory.build();
+ }
+
+ /**
+ * Put data to DS.
+ *
+ * @param path
+ * path of data
+ * @param schemaContext
+ * {@link SchemaContext}
+ * @param domTransactionChain
+ * write transaction
+ * @param data
+ * data
+ * @param point
+ * query parameter
+ * @param insert
+ * query parameter
+ * @return {@link CheckedFuture}
+ */
+ private static CheckedFuture<Void, TransactionCommitFailedException> submitData(final YangInstanceIdentifier path,
+ final SchemaContext schemaContext, final DOMTransactionChain domTransactionChain,
+ final NormalizedNode<?, ?> data, final String insert, final String point) {
+ final DOMDataReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction();
+ if (insert == null) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ final DataSchemaNode schemaNode = checkListAndOrderedType(schemaContext, path);
+ switch (insert) {
+ case "first":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
+ simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
+ schemaContext, data);
+ listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
+ schemaContext, readList);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent());
+ simplePut(LogicalDatastoreType.CONFIGURATION, path, newReadWriteTransaction,
+ schemaContext, data);
+ listPut(LogicalDatastoreType.CONFIGURATION, path.getParent(), newReadWriteTransaction,
+ schemaContext, readLeafList);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ case "last":
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ case "before":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path,
+ data, schemaContext, point, readList, true);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+ path, data, schemaContext, point, readLeafList, true);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ case "after":
+ if (schemaNode instanceof ListSchemaNode) {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+ final OrderedMapNode readList = (OrderedMapNode) readData;
+ if ((readList == null) || readList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ insertWithPointListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+ path, data, schemaContext, point, readList, false);
+ return newReadWriteTransaction.submit();
+ }
+ } else {
+ final NormalizedNode<?, ?> readData =
+ readList(path, schemaContext, domTransactionChain, schemaNode);
+
+ final OrderedLeafSetNode<?> readLeafList = (OrderedLeafSetNode<?>) readData;
+ if ((readLeafList == null) || readLeafList.getValue().isEmpty()) {
+ return makePut(path, schemaContext, newReadWriteTransaction, data);
+ } else {
+ insertWithPointLeafListPut(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION,
+ path, data, schemaContext, point, readLeafList, true);
+ return newReadWriteTransaction.submit();
+ }
+ }
+ default:
+ throw new RestconfDocumentedException(
+ "Used bad value of insert parameter. Possible values are first, last, before or after, "
+ + "but was: " + insert);
+ }
+ }
+ }
+
+ public static NormalizedNode<?, ?> readList(final YangInstanceIdentifier path, final SchemaContext schemaContext,
+ final DOMTransactionChain domTransactionChain, final DataSchemaNode schemaNode) {
+ final InstanceIdentifierContext<?> iid = new InstanceIdentifierContext<SchemaNode>(
+ path.getParent(), schemaNode, null, schemaContext);
+ final TransactionVarsWrapper transactionNode =
+ new TransactionVarsWrapper(iid, null, domTransactionChain);
+ final NormalizedNode<?, ?> readData = ReadDataTransactionUtil
+ .readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode, schemaContext);
+ return readData;
+ }
+
+ private static void insertWithPointLeafListPut(final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
+ final OrderedLeafSetNode<?> readLeafList, final boolean before) {
+ rwTransaction.delete(datastore, path.getParent());
+ final InstanceIdentifierContext<?> instanceIdentifier =
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
+ int lastItemPosition = 0;
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
+ if (nodeChild.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final LeafSetEntryNode<?> nodeChild : readLeafList.getValue()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ simplePut(datastore, path, rwTransaction, schemaContext, data);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(nodeChild.getIdentifier());
+ rwTransaction.put(datastore, childPath, nodeChild);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static void insertWithPointListPut(final DOMDataReadWriteTransaction writeTx,
+ final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> data, final SchemaContext schemaContext, final String point,
+ final OrderedMapNode readList, final boolean before) {
+ writeTx.delete(datastore, path.getParent());
+ final InstanceIdentifierContext<?> instanceIdentifier =
+ ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.absent());
+ int lastItemPosition = 0;
+ for (final MapEntryNode mapEntryNode : readList.getValue()) {
+ if (mapEntryNode.getIdentifier().equals(instanceIdentifier.getInstanceIdentifier().getLastPathArgument())) {
+ break;
+ }
+ lastItemPosition++;
+ }
+ if (!before) {
+ lastItemPosition++;
+ }
+ int lastInsertedPosition = 0;
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path.getParent());
+ writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ for (final MapEntryNode mapEntryNode : readList.getValue()) {
+ if (lastInsertedPosition == lastItemPosition) {
+ simplePut(datastore, path, writeTx, schemaContext, data);
+ }
+ final YangInstanceIdentifier childPath = path.getParent().node(mapEntryNode.getIdentifier());
+ writeTx.put(datastore, childPath, mapEntryNode);
+ lastInsertedPosition++;
+ }
+ }
+
+ private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
+ final OrderedLeafSetNode<?> payload) {
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
+ for (final LeafSetEntryNode<?> child : ((LeafSetNode<?>) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ writeTx.put(datastore, childPath, child);
+ }
+ }
+
+ private static void listPut(final LogicalDatastoreType datastore, final YangInstanceIdentifier path,
+ final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
+ final OrderedMapNode payload) {
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ writeTx.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
+ for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ writeTx.put(datastore, childPath, child);
+ }
+ }
+
+ private static void simplePut(final LogicalDatastoreType configuration, final YangInstanceIdentifier path,
+ final DOMDataReadWriteTransaction writeTx, final SchemaContext schemaContext,
+ final NormalizedNode<?, ?> data) {
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
+ writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ }
+
+ private static CheckedFuture<Void, TransactionCommitFailedException> makePut(final YangInstanceIdentifier path,
+ final SchemaContext schemaContext, final DOMDataWriteTransaction writeTx, final NormalizedNode<?, ?> data) {
+ TransactionUtil.ensureParentsByMerge(path, schemaContext, writeTx);
+ writeTx.put(LogicalDatastoreType.CONFIGURATION, path, data);
+ return writeTx.submit();
+ }
+
+ public static DataSchemaNode checkListAndOrderedType(final SchemaContext ctx, final YangInstanceIdentifier path) {
+ final YangInstanceIdentifier parent = path.getParent();
+ final DataSchemaContextNode<?> node = DataSchemaContextTree.from(ctx).getChild(parent);
+ 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");
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAMS_PATH;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH_PART;
+import com.google.common.base.Optional;
+import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.WriterParameters;
+import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter;
+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.common.QNameModule;
+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.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.LeafNode;
+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.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+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.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Util class for read data from data store via transaction.
+ * <ul>
+ * <li>config
+ * <li>state
+ * <li>all (config + state)
+ * </ul>
+ *
+ */
+public final class ReadDataTransactionUtil {
+
+ private ReadDataTransactionUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Parse parameters from URI request and check their types and values.
+ *
+ *
+ * @param identifier
+ * {@link InstanceIdentifierContext}
+ * @param uriInfo
+ * URI info
+ * @param tagged
+ * set tagged for {@link WriterParameters}
+ * @return {@link WriterParameters}
+ */
+ @Nonnull
+ public static WriterParameters parseUriParameters(@Nonnull final InstanceIdentifierContext<?> identifier,
+ @Nullable final UriInfo uriInfo, final boolean tagged) {
+ return parseParams(identifier, uriInfo, tagged);
+ }
+
+ /**
+ * Parse parameters from URI request and check their types and values.
+ *
+ *
+ * @param identifier
+ * {@link InstanceIdentifierContext}
+ * @param uriInfo
+ * URI info
+ * @return {@link WriterParameters}
+ */
+ @Nonnull
+ public static WriterParameters parseUriParameters(@Nonnull final InstanceIdentifierContext<?> identifier,
+ @Nullable final UriInfo uriInfo) {
+ return parseParams(identifier, uriInfo, false);
+ }
+
+ private static WriterParameters parseParams(final InstanceIdentifierContext<?> identifier, final UriInfo uriInfo,
+ final boolean tagged) {
+ final WriterParametersBuilder builder = new WriterParametersBuilder();
+ builder.setTagged(tagged);
+
+ if (uriInfo == null) {
+ return builder.build();
+ }
+
+ // check only allowed parameters
+ ParametersUtil.checkParametersTypes(
+ RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+ uriInfo.getQueryParameters().keySet(),
+ RestconfDataServiceConstant.ReadData.CONTENT,
+ RestconfDataServiceConstant.ReadData.DEPTH,
+ RestconfDataServiceConstant.ReadData.FIELDS, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
+
+ // read parameters from URI or set default values
+ final List<String> content = uriInfo.getQueryParameters().getOrDefault(
+ RestconfDataServiceConstant.ReadData.CONTENT,
+ Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
+ final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
+ RestconfDataServiceConstant.ReadData.DEPTH,
+ Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
+ // fields
+ final List<String> fields = uriInfo.getQueryParameters().getOrDefault(
+ RestconfDataServiceConstant.ReadData.FIELDS,
+ Collections.emptyList());
+
+ // parameter can be in URI at most once
+ ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
+ ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
+ ParametersUtil.checkParameterCount(fields, RestconfDataServiceConstant.ReadData.FIELDS);
+
+ // check and set content
+ final String contentValue = content.get(0);
+ if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
+ if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
+ && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + contentValue, null,
+ "The content parameter value must be either config, nonconfig or all (default)"));
+ }
+ }
+
+ builder.setContent(content.get(0));
+
+ // check and set depth
+ if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
+ final Integer value = Ints.tryParse(depth.get(0));
+
+ if ((value == null)
+ || (!((value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH)
+ && (value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH)))) {
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + depth, null,
+ "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
+ } else {
+ builder.setDepth(value);
+ }
+ }
+
+ // check and set fields
+ if (!fields.isEmpty()) {
+ builder.setFields(ParserFieldsParameter.parseFieldsParameter(identifier, fields.get(0)));
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Read specific type of data from data store via transaction.
+ *
+ * @param valueOfContent
+ * type of data to read (config, state, all)
+ * @param transactionNode
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param schemaContext
+ * @return {@link NormalizedNode}
+ */
+ @Nullable
+ public static NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
+ @Nonnull final TransactionVarsWrapper transactionNode, final SchemaContext schemaContext) {
+ return readData(valueOfContent, transactionNode, null, schemaContext);
+ }
+
+ /**
+ * Read specific type of data from data store via transaction.
+ *
+ * @param valueOfContent
+ * type of data to read (config, state, all)
+ * @param transactionNode
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param withDefa
+ * vaule of with-defaults parameter
+ * @param ctx
+ * @return {@link NormalizedNode}
+ */
+ @Nullable
+ public static NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
+ @Nonnull final TransactionVarsWrapper transactionNode, final String withDefa, final SchemaContext ctx) {
+ switch (valueOfContent) {
+ case RestconfDataServiceConstant.ReadData.CONFIG:
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
+ if (withDefa == null) {
+ return readDataViaTransaction(transactionNode);
+ } else {
+ return prepareDataByParamWithDef(readDataViaTransaction(transactionNode),
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx);
+ }
+ case RestconfDataServiceConstant.ReadData.NONCONFIG:
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
+ return readDataViaTransaction(transactionNode);
+
+ case RestconfDataServiceConstant.ReadData.ALL:
+ return readAllData(transactionNode, withDefa, ctx);
+
+ default:
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + valueOfContent, null,
+ "The content parameter value must be either config, nonconfig or all (default)"));
+ }
+ }
+
+ /**
+ * Read specific type of data from data store via transaction and if identifier read data from
+ * streams then put streams from actual schema context to datastore.
+ *
+ * @param identifier
+ * identifier of data to read
+ * @param content
+ * type of data to read (config, state, all)
+ * @param transactionNode
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param withDefa
+ * vaule of with-defaults parameter
+ * @param schemaContextRef
+ * schema context
+ * @param uriInfo
+ * uri info
+ * @return {@link NormalizedNode}
+ */
+ public static NormalizedNode<?, ?> readData(final String identifier, final String content,
+ final TransactionVarsWrapper transactionNode, final String withDefa,
+ final SchemaContextRef schemaContextRef, final UriInfo uriInfo) {
+ final SchemaContext schemaContext = schemaContextRef.get();
+ if (identifier.contains(STREAMS_PATH) && !identifier.contains(STREAM_PATH_PART)) {
+ final DOMDataReadWriteTransaction wTx = transactionNode.getTransactionChain().newReadWriteTransaction();
+ final boolean exist = SubscribeToStreamUtil.checkExist(schemaContext, wTx);
+
+ for (final NotificationDefinition notificationDefinition : schemaContextRef.get().getNotifications()) {
+ final List<NotificationListenerAdapter> notifiStreamXML =
+ CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContextRef,
+ NotificationOutputType.XML.getName());
+ final List<NotificationListenerAdapter> notifiStreamJSON =
+ CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContextRef,
+ NotificationOutputType.JSON.getName());
+ notifiStreamJSON.addAll(notifiStreamXML);
+
+ for (final NotificationListenerAdapter listener : notifiStreamJSON) {
+ final URI uri = SubscribeToStreamUtil.prepareUriByStreamName(uriInfo, listener.getStreamName());
+ final NormalizedNode mapToStreams =
+ RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring(
+ listener.getSchemaPath().getLastComponent(), schemaContext.getNotifications(),
+ null, listener.getOutputType(), uri,
+ SubscribeToStreamUtil.getMonitoringModule(schemaContext), exist);
+ SubscribeToStreamUtil.writeDataToDS(schemaContext,
+ listener.getSchemaPath().getLastComponent().getLocalName(), wTx, exist,
+ mapToStreams);
+ }
+ }
+ SubscribeToStreamUtil.submitData(wTx);
+ }
+ return readData(content, transactionNode, withDefa, schemaContext);
+ }
+
+ private static NormalizedNode<?, ?> prepareDataByParamWithDef(final NormalizedNode<?, ?> result,
+ final YangInstanceIdentifier path, final String withDefa, final SchemaContext ctx) {
+ boolean trim;
+ switch (withDefa) {
+ case "trim":
+ trim = true;
+ break;
+ case "explicit":
+ trim = false;
+ break;
+ default:
+ throw new RestconfDocumentedException("");
+ }
+
+ final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
+ final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
+ if (result instanceof ContainerNode) {
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder =
+ Builders.containerBuilder((ContainerSchemaNode) baseSchemaNode);
+ buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim);
+ return builder.build();
+ } else {
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder =
+ Builders.mapEntryBuilder((ListSchemaNode) baseSchemaNode);
+ buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) baseSchemaNode).getKeyDefinition());
+ return builder.build();
+ }
+ }
+
+ private static void buildMapEntryBuilder(
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder,
+ final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim, final List<QName> keys) {
+ for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> childBuilder =
+ Builders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim);
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
+ Builders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final String defaultVal = ((LeafSchemaNode) childSchema).getDefault();
+ final String nodeVal = ((LeafNode<String>) child).getValue();
+ final NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ Builders.leafBuilder((LeafSchemaNode) childSchema);
+ if (keys.contains(child.getNodeType())) {
+ leafBuilder.withValue(((LeafNode) child).getValue());
+ builder.withChild(leafBuilder.build());
+ } else {
+ if (trim) {
+ if ((defaultVal == null) || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(((LeafNode) child).getValue());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if ((defaultVal != null) && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(((LeafNode) child).getValue());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void buildList(final CollectionNodeBuilder<MapEntryNode, MapNode> builder, final MapNode result,
+ final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim,
+ final List<QName> keys) {
+ for (final MapEntryNode mapEntryNode : result.getValue()) {
+ final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(actualNode).getDataSchemaNode();
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
+ Builders.mapEntryBuilder((ListSchemaNode) childSchema);
+ buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys);
+ builder.withChild(mapEntryBuilder.build());
+ }
+ }
+
+ private static void buildCont(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder,
+ final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree,
+ final YangInstanceIdentifier actualPath, final boolean trim) {
+ for (final DataContainerChild<? extends PathArgument, ?> child : result.getValue()) {
+ final YangInstanceIdentifier path = actualPath.node(child.getIdentifier());
+ final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode();
+ if (child instanceof ContainerNode) {
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builderChild =
+ Builders.containerBuilder((ContainerSchemaNode) childSchema);
+ buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim);
+ builder.withChild(builderChild.build());
+ } else if (child instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, MapNode> childBuilder =
+ Builders.mapBuilder((ListSchemaNode) childSchema);
+ buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim,
+ ((ListSchemaNode) childSchema).getKeyDefinition());
+ builder.withChild(childBuilder.build());
+ } else if (child instanceof LeafNode) {
+ final String defaultVal = ((LeafSchemaNode) childSchema).getDefault();
+ final String nodeVal = ((LeafNode<String>) child).getValue();
+ final NormalizedNodeAttrBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
+ Builders.leafBuilder((LeafSchemaNode) childSchema);
+ if (trim) {
+ if ((defaultVal == null) || !defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(((LeafNode) child).getValue());
+ builder.withChild(leafBuilder.build());
+ }
+ } else {
+ if ((defaultVal != null) && defaultVal.equals(nodeVal)) {
+ leafBuilder.withValue(((LeafNode) child).getValue());
+ builder.withChild(leafBuilder.build());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * If is set specific {@link LogicalDatastoreType} in
+ * {@link TransactionVarsWrapper}, then read this type of data from DS. If
+ * don't, we have to read all data from DS (state + config)
+ *
+ * @param transactionNode
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @return {@link NormalizedNode}
+ */
+ @Nullable
+ private static NormalizedNode<?, ?> readDataViaTransaction(
+ @Nonnull final TransactionVarsWrapper transactionNode) {
+ final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
+ try (DOMDataReadOnlyTransaction tx = transactionNode.getTransactionChain().newReadOnlyTransaction()) {
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = tx.read(
+ transactionNode.getLogicalDatastoreType(),
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier());
+ FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+ dataFactory);
+ }
+ return dataFactory.build();
+ }
+
+ /**
+ * Read config and state data, then map them.
+ *
+ * @param transactionNode
+ * {@link TransactionVarsWrapper} - wrapper for variables
+ * @param withDefa
+ * with-defaults parameter
+ * @param ctx
+ * @return {@link NormalizedNode}
+ */
+ @Nullable
+ private static NormalizedNode<?, ?> readAllData(@Nonnull final TransactionVarsWrapper transactionNode,
+ final String withDefa, final SchemaContext ctx) {
+ // PREPARE STATE DATA NODE
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
+ final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(transactionNode);
+
+ // PREPARE CONFIG DATA NODE
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
+ final NormalizedNode<?, ?> configDataNode;
+ if (withDefa == null) {
+ configDataNode = readDataViaTransaction(transactionNode);
+ } else {
+ configDataNode = prepareDataByParamWithDef(readDataViaTransaction(transactionNode),
+ transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa, ctx);
+ }
+
+ // if no data exists
+ if ((stateDataNode == null) && (configDataNode == null)) {
+ return null;
+ }
+
+ // return config data
+ if (stateDataNode == null) {
+ return configDataNode;
+ }
+
+ // return state data
+ if (configDataNode == null) {
+ return stateDataNode;
+ }
+
+ // merge data from config and state
+ return mapNode(stateDataNode, configDataNode);
+ }
+
+ /**
+ * Map data by type of read node.
+ *
+ * @param stateDataNode
+ * data node of state data
+ * @param configDataNode
+ * data node of config data
+ * @return {@link NormalizedNode}
+ */
+ @Nonnull
+ private static NormalizedNode<?, ?> mapNode(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+ @Nonnull final NormalizedNode<?, ?> configDataNode) {
+ validPossibilityOfMergeNodes(stateDataNode, configDataNode);
+ if (configDataNode instanceof RpcDefinition) {
+ return prepareRpcData(configDataNode, stateDataNode);
+ } else {
+ return prepareData(configDataNode, stateDataNode);
+ }
+ }
+
+ /**
+ * Valid of can be data merged together.
+ *
+ * @param stateDataNode
+ * data node of state data
+ * @param configDataNode
+ * data node of config data
+ */
+ private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+ @Nonnull final NormalizedNode<?, ?> configDataNode) {
+ final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
+ final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
+ if (moduleOfStateData != moduleOfConfigData) {
+ throw new RestconfDocumentedException("It is not possible to merge ");
+ }
+ }
+
+ /**
+ * Prepare and map data for rpc.
+ *
+ * @param configDataNode
+ * data node of config data
+ * @param stateDataNode
+ * data node of state data
+ * @return {@link NormalizedNode}
+ */
+ @Nonnull
+ private static NormalizedNode<?, ?> prepareRpcData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+ @Nonnull final NormalizedNode<?, ?> stateDataNode) {
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
+ .mapEntryBuilder();
+ mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
+
+ // MAP CONFIG DATA
+ mapRpcDataNode(configDataNode, mapEntryBuilder);
+ // MAP STATE DATA
+ mapRpcDataNode(stateDataNode, mapEntryBuilder);
+
+ return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
+ }
+
+ /**
+ * Map node to map entry builder.
+ *
+ * @param dataNode
+ * data node
+ * @param mapEntryBuilder
+ * builder for mapping data
+ */
+ private static void mapRpcDataNode(@Nonnull final NormalizedNode<?, ?> dataNode,
+ @Nonnull final DataContainerNodeBuilder<
+ NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
+ ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
+ }
+
+ /**
+ * Prepare and map all data from DS.
+ *
+ * @param configDataNode
+ * data node of config data
+ * @param stateDataNode
+ * data node of state data
+ * @return {@link NormalizedNode}
+ */
+ @Nonnull
+ private static NormalizedNode<?, ?> prepareData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+ @Nonnull final NormalizedNode<?, ?> stateDataNode) {
+ if (configDataNode instanceof MapNode) {
+ final CollectionNodeBuilder<MapEntryNode, MapNode> builder = ImmutableNodes
+ .mapNodeBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
+
+ mapValueToBuilder(
+ ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
+
+ return builder.build();
+ } else if (configDataNode instanceof MapEntryNode) {
+ final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
+ .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
+
+ mapValueToBuilder(
+ ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
+
+ return builder.build();
+ } else if (configDataNode instanceof ContainerNode) {
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = Builders
+ .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
+
+ mapValueToBuilder(
+ ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
+
+ return builder.build();
+ } else if (configDataNode instanceof AugmentationNode) {
+ final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
+ .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
+
+ mapValueToBuilder(((AugmentationNode) configDataNode).getValue(),
+ ((AugmentationNode) stateDataNode).getValue(), builder);
+
+ return builder.build();
+ } else if (configDataNode instanceof ChoiceNode) {
+ final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
+ .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
+
+ mapValueToBuilder(
+ ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
+
+ return builder.build();
+ } else if (configDataNode instanceof LeafNode) {
+ return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
+ } else {
+ throw new RestconfDocumentedException("Bad type of node.");
+ }
+ }
+
+ /**
+ * Map value from container node to builder.
+ *
+ * @param configData
+ * collection of config data nodes
+ * @param stateData
+ * collection of state data nodes
+ * @param builder
+ * builder
+ */
+ private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapValueToBuilder(
+ @Nonnull final Collection<T> configData,
+ @Nonnull final Collection<T> stateData,
+ @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+ final Map<PathArgument, T> configMap = configData.stream().collect(
+ Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
+ final Map<PathArgument, T> stateMap = stateData.stream().collect(
+ Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
+
+ // merge config and state data of children with different identifiers
+ mapDataToBuilder(configMap, stateMap, builder);
+
+ // merge config and state data of children with the same identifiers
+ mergeDataToBuilder(configMap, stateMap, builder);
+ }
+
+ /**
+ * Map data with different identifiers to builder. Data with different identifiers can be just added
+ * as childs to parent node.
+ *
+ * @param configMap
+ * map of config data nodes
+ * @param stateMap
+ * map of state data nodes
+ * @param builder
+ * - builder
+ */
+ private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapDataToBuilder(
+ @Nonnull final Map<PathArgument, T> configMap,
+ @Nonnull final Map<PathArgument, T> stateMap,
+ @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+ configMap.entrySet().stream().filter(x -> !stateMap.containsKey(x.getKey())).forEach(
+ y -> builder.addChild(y.getValue()));
+ stateMap.entrySet().stream().filter(x -> !configMap.containsKey(x.getKey())).forEach(
+ y -> builder.addChild(y.getValue()));
+ }
+
+ /**
+ * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to
+ * go one level down with {@code prepareData} method.
+ *
+ * @param configMap
+ * immutable config data
+ * @param stateMap
+ * immutable state data
+ * @param builder
+ * - builder
+ */
+ @SuppressWarnings("unchecked")
+ private static <T extends NormalizedNode<? extends PathArgument, ?>> void mergeDataToBuilder(
+ @Nonnull final Map<PathArgument, T> configMap,
+ @Nonnull final Map<PathArgument, T> stateMap,
+ @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+ // it is enough to process only config data because operational contains the same data
+ configMap.entrySet().stream().filter(x -> stateMap.containsKey(x.getKey())).forEach(
+ y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+/**
+ * Common util class for resolve enum from String.
+ *
+ */
+public final class ResolveEnumUtil {
+
+ private ResolveEnumUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Resolve specific type of enum by value.
+ *
+ * @param clazz
+ * enum
+ * @param value
+ * string of enum
+ * @return - enum
+ */
+ public static <T> T resolveEnum(final Class<T> clazz, final String value) {
+ for (final T t : clazz.getEnumConstants()) {
+ if (((Enum<?>) t).name().equals(value)) {
+ return t;
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import java.net.URI;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+import org.apache.commons.lang3.builder.Builder;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+final class ResponseFactory extends FutureDataFactory<Void> implements Builder<Response> {
+
+ private ResponseBuilder responseBuilder;
+
+ ResponseFactory(final NormalizedNode<?, ?> readData) {
+ final Status status = prepareStatus(readData);
+ this.responseBuilder = Response.status(status);
+ }
+
+ ResponseFactory(final NormalizedNode<?, ?> readData, final URI location) {
+ final Status status = prepareStatus(readData);
+ this.responseBuilder = Response.status(status);
+ this.responseBuilder.location(location);
+ }
+
+ ResponseFactory() {
+ this.responseBuilder = Response.status(Status.OK);
+ }
+
+ @Override
+ public Response build() {
+ if (getFailureStatus()) {
+ responseBuilder = responseBuilder.status(Response.Status.INTERNAL_SERVER_ERROR);
+ }
+ return this.responseBuilder.build();
+ }
+
+ private static Status prepareStatus(final NormalizedNode<?, ?> readData) {
+ return readData != null ? Status.OK : Status.CREATED;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+/**
+ * Constants for RestconfDataService.
+ *
+ */
+public final class RestconfDataServiceConstant {
+
+ public static final QName NETCONF_BASE_QNAME;
+
+ static {
+ try {
+ NETCONF_BASE_QNAME = QName.create(
+ QNameModule.create(new URI(PutData.NETCONF_BASE), null), PutData.NETCONF_BASE_PAYLOAD_NAME);
+ } catch (final URISyntaxException e) {
+ final String errMsg = "It wasn't possible to create instance of URI class with " + PutData.NETCONF_BASE
+ + " URI";
+ throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+ }
+
+ private RestconfDataServiceConstant() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Constants for read data.
+ *
+ */
+ public final class ReadData {
+ // URI parameters
+ public static final String CONTENT = "content";
+ public static final String DEPTH = "depth";
+ public static final String FIELDS = "fields";
+
+ // content values
+ public static final String CONFIG = "config";
+ public static final String ALL = "all";
+ public static final String NONCONFIG = "nonconfig";
+
+ // depth values
+ public static final String UNBOUNDED = "unbounded";
+ public static final int MIN_DEPTH = 1;
+ public static final int MAX_DEPTH = 65535;
+
+ public static final String READ_TYPE_TX = "READ";
+ public static final String WITH_DEFAULTS = "with-defaults";
+
+ private ReadData() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+ }
+
+ /**
+ * Constants for data to put.
+ *
+ */
+ public final class PutData {
+ public static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
+ public static final String NETCONF_BASE_PAYLOAD_NAME = "data";
+ public static final String PUT_TX_TYPE = "PUT";
+
+ private PutData() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+ }
+
+ /**
+ * Constants for data to post.
+ *
+ */
+ public final class PostData {
+ public static final String POST_TX_TYPE = "POST";
+
+ private PostData() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+ }
+
+ /**
+ * Constants for data to delete.
+ *
+ */
+ public final class DeleteData {
+ public static final String DELETE_TX_TYPE = "DELETE";
+
+ private DeleteData() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+ }
+
+ /**
+ * Constants for data to yang patch.
+ *
+ */
+ public final class PatchData {
+ public static final String PATCH_TX_TYPE = "Patch";
+
+ private PatchData() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.concurrent.CancellationException;
+import javax.ws.rs.core.Response.Status;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.handlers.RpcServiceHandler;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util class for rpc.
+ *
+ */
+public class RestconfInvokeOperationsUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfInvokeOperationsUtil.class);
+
+ private RestconfInvokeOperationsUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Invoking rpc via mount point.
+ *
+ * @param mountPoint
+ * mount point
+ * @param data
+ * input data
+ * @param schemaPath
+ * schema path of data
+ * @return {@link CheckedFuture}
+ */
+ public static DOMRpcResult invokeRpcViaMountPoint(final DOMMountPoint mountPoint, final NormalizedNode<?, ?> data,
+ final SchemaPath schemaPath) {
+ final Optional<DOMRpcService> mountPointService = mountPoint.getService(DOMRpcService.class);
+ if (mountPointService.isPresent()) {
+ final CheckedFuture<DOMRpcResult, DOMRpcException> rpc = mountPointService.get().invokeRpc(schemaPath,
+ data);
+ return prepareResult(rpc);
+ }
+ final String errmsg = "RPC service is missing.";
+ LOG.debug(errmsg);
+ throw new RestconfDocumentedException(errmsg);
+ }
+
+ /**
+ * Invoke rpc.
+ *
+ * @param data
+ * input data
+ * @param schemaPath
+ * schema path of data
+ * @param rpcServiceHandler
+ * rpc service handler to invoke rpc
+ * @return {@link CheckedFuture}
+ */
+ public static DOMRpcResult invokeRpc(final NormalizedNode<?, ?> data, final SchemaPath schemaPath,
+ final RpcServiceHandler rpcServiceHandler) {
+ final DOMRpcService rpcService = rpcServiceHandler.get();
+ if (rpcService == null) {
+ throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
+ }
+
+ final CheckedFuture<DOMRpcResult, DOMRpcException> rpc = rpcService.invokeRpc(schemaPath, data);
+ return prepareResult(rpc);
+ }
+
+ /**
+ * Check the validity of the result.
+ *
+ * @param response
+ * response of rpc
+ * @return {@link DOMRpcResult} result
+ */
+ public static DOMRpcResult checkResponse(final DOMRpcResult response) {
+ if (response == null) {
+ return null;
+ }
+ try {
+ if (response.getErrors().isEmpty()) {
+ return response;
+ }
+ LOG.debug("RpcError message", response.getErrors());
+ throw new RestconfDocumentedException("RPCerror message ", null, response.getErrors());
+ } 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 DOMRpcResult prepareResult(final CheckedFuture<DOMRpcResult, DOMRpcException> rpc) {
+ final RpcResultFactory dataFactory = new RpcResultFactory();
+ FutureCallbackTx.addCallback(rpc, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
+ return dataFactory.build();
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.collect.ImmutableSet;
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Date;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Constants for streams.
+ *
+ */
+public final class RestconfStreamsConstants {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfStreamsConstants.class);
+
+ public static final String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
+
+ public static final String DATASTORE_PARAM_NAME = "datastore";
+
+ private static final URI NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT = URI.create("urn:sal:restconf:event:subscription");
+
+ public static final QNameModule SAL_REMOTE_AUGMENT;
+
+ static {
+ final Date eventSubscriptionAugRevision;
+ try {
+ eventSubscriptionAugRevision = SimpleDateFormatUtil.getRevisionFormat().parse("2014-07-08");
+ } catch (final ParseException e) {
+ final String errMsg = "It wasn't possible to convert revision date of sal-remote-augment to date";
+ LOG.debug(errMsg);
+ throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+ SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT, eventSubscriptionAugRevision)
+ .intern();
+ }
+
+ public 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 DataChangeScope DEFAULT_SCOPE = DataChangeScope.BASE;
+
+ public static final LogicalDatastoreType DEFAULT_DS = LogicalDatastoreType.CONFIGURATION;
+
+ public static final String SCOPE_PARAM_NAME = "scope";
+
+ public static final char EQUAL = ParserBuilderConstants.Deserializer.EQUAL;
+
+ public static final String DS_URI = RestconfConstants.SLASH + DATASTORE_PARAM_NAME + EQUAL;
+
+ public static final String SCOPE_URI = RestconfConstants.SLASH + SCOPE_PARAM_NAME + EQUAL;
+
+ public static final int NOTIFICATION_PORT = 8181;
+
+ public static final String SCHEMA_SUBSCIBRE_URI = "ws";
+
+ public static final CharSequence DATA_SUBSCR = "data-change-event-subscription";
+ public static final CharSequence CREATE_DATA_SUBSCR = "create-" + DATA_SUBSCR;
+
+ public static final CharSequence NOTIFICATION_STREAM = "notification-stream";
+ public static final CharSequence CREATE_NOTIFICATION_STREAM = "create-" + NOTIFICATION_STREAM;
+
+ public static final String STREAMS_PATH = "ietf-restconf-monitoring:restconf-state/streams";
+ public static final String STREAM_PATH_PART = "/stream=";
+ public static final String STREAM_PATH = STREAMS_PATH + STREAM_PATH_PART;
+ public static final String STREAM_ACCESS_PATH_PART = "/access=";
+ public static final String STREAM_LOCATION_PATH_PART = "/location";
+
+ private RestconfStreamsConstants() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import org.apache.commons.lang3.builder.Builder;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+
+public class RpcResultFactory extends FutureDataFactory<DOMRpcResult> implements Builder<DOMRpcResult> {
+
+ @Override
+ public DOMRpcResult build() {
+ return this.result;
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule;
+import org.opendaylight.restconf.nb.rfc8040.handlers.NotificationServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.RestconfStreamsSubscriptionServiceImpl.HandlersHolder;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.RestconfStreamsSubscriptionServiceImpl.NotificationQueryParams;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.Notificator;
+import org.opendaylight.restconf.nb.rfc8040.streams.websockets.WebSocketServer;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
+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.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+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.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Subscribe to stream util class.
+ *
+ */
+public final class SubscribeToStreamUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SubscribeToStreamUtil.class);
+ 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 SubscribeToStreamUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Register listeners by streamName in identifier to listen to yang
+ * notifications, put or delete info about listener to DS according to
+ * ietf-restconf-monitoring.
+ *
+ * @param identifier
+ * identifier as stream name
+ * @param uriInfo
+ * for getting base URI information
+ * @param notificationQueryParams
+ * query parameters of notification
+ * @param handlersHolder
+ * holder of handlers for notifications
+ * @return location for listening
+ */
+ @SuppressWarnings("rawtypes")
+ public static URI notifYangStream(final String identifier, final UriInfo uriInfo,
+ final NotificationQueryParams notificationQueryParams, final HandlersHolder handlersHolder) {
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+ if (Strings.isNullOrEmpty(streamName)) {
+ throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (identifier.contains(RestconfConstants.SLASH + NotificationOutputType.JSON.getName())) {
+ listeners = pickSpecificListenerByOutput(listeners, NotificationOutputType.JSON.getName());
+ } else {
+ listeners = pickSpecificListenerByOutput(listeners, NotificationOutputType.XML.getName());
+ }
+ if ((listeners == null) || listeners.isEmpty()) {
+ throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT);
+ }
+
+ final DOMDataReadWriteTransaction wTx =
+ handlersHolder.getTransactionChainHandler().get().newReadWriteTransaction();
+ final SchemaContext schemaContext = handlersHolder.getSchemaHandler().get();
+ final boolean exist = checkExist(schemaContext, wTx);
+
+ final URI uri = prepareUriByStreamName(uriInfo, streamName);
+ for (final NotificationListenerAdapter listener : listeners) {
+ registerToListenNotification(listener, handlersHolder.getNotificationServiceHandler());
+ listener.setQueryParams(notificationQueryParams.getStart(), notificationQueryParams.getStop(),
+ notificationQueryParams.getFilter(), false);
+ listener.setCloseVars(handlersHolder.getTransactionChainHandler(), handlersHolder.getSchemaHandler());
+ final NormalizedNode mapToStreams = RestconfMappingNodeUtil
+ .mapYangNotificationStreamByIetfRestconfMonitoring(listener.getSchemaPath().getLastComponent(),
+ schemaContext.getNotifications(), notificationQueryParams.getStart(),
+ listener.getOutputType(), uri, getMonitoringModule(schemaContext), exist);
+ writeDataToDS(schemaContext, listener.getSchemaPath().getLastComponent().getLocalName(), wTx, exist,
+ mapToStreams);
+ }
+ submitData(wTx);
+
+ return uri;
+ }
+
+ static List<NotificationListenerAdapter>
+ pickSpecificListenerByOutput(final List<NotificationListenerAdapter> listeners, final String outputType) {
+ for (final NotificationListenerAdapter notificationListenerAdapter : listeners) {
+ if (notificationListenerAdapter.getOutputType().equals(outputType)) {
+ final List<NotificationListenerAdapter> list = new ArrayList<>();
+ list.add(notificationListenerAdapter);
+ return list;
+ }
+ }
+ return listeners;
+ }
+
+ /**
+ * Prepare InstanceIdentifierContext for Location leaf.
+ *
+ * @param schemaHandler
+ * schemaContext handler
+ * @return InstanceIdentifier of Location leaf
+ */
+ public static InstanceIdentifierContext<?> prepareIIDSubsStreamOutput(final SchemaContextHandler schemaHandler) {
+ final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi");
+ final DataSchemaNode location = ((ContainerSchemaNode) schemaHandler.get()
+ .findModuleByNamespaceAndRevision(qnameBase.getNamespace(), qnameBase.getRevision())
+ .getDataChildByName(qnameBase)).getDataChildByName(QName.create(qnameBase, "location"));
+ final List<PathArgument> path = new ArrayList<>();
+ path.add(NodeIdentifier.create(qnameBase));
+ path.add(NodeIdentifier.create(QName.create(qnameBase, "location")));
+
+ return new InstanceIdentifierContext<SchemaNode>(YangInstanceIdentifier.create(path), location, null,
+ schemaHandler.get());
+ }
+
+ /**
+ * Register listener by streamName in identifier to listen to data change
+ * notifications, put or delete info about listener to DS according to
+ * ietf-restconf-monitoring.
+ *
+ * @param identifier
+ * identifier as stream name
+ * @param uriInfo
+ * for getting base URI information
+ * @param notificationQueryParams
+ * query parameters of notification
+ * @param handlersHolder
+ * holder of handlers for notifications
+ * @return location for listening
+ */
+ @SuppressWarnings("rawtypes")
+ public static URI notifiDataStream(final String identifier, final UriInfo uriInfo,
+ final NotificationQueryParams notificationQueryParams, final HandlersHolder handlersHolder) {
+ final Map<String, String> mapOfValues = SubscribeToStreamUtil.mapValuesFromUri(identifier);
+
+ final LogicalDatastoreType ds = SubscribeToStreamUtil.parseURIEnum(LogicalDatastoreType.class,
+ mapOfValues.get(RestconfStreamsConstants.DATASTORE_PARAM_NAME));
+ if (ds == null) {
+ final String msg = "Stream name doesn't contains datastore value (pattern /datastore=)";
+ LOG.debug(msg);
+ throw new RestconfDocumentedException(msg, ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+
+ final DataChangeScope scope = SubscribeToStreamUtil.parseURIEnum(DataChangeScope.class,
+ mapOfValues.get(RestconfStreamsConstants.SCOPE_PARAM_NAME));
+ if (scope == null) {
+ final String msg = "Stream name doesn't contains datastore value (pattern /scope=)";
+ LOG.warn(msg);
+ throw new RestconfDocumentedException(msg, ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
+ }
+
+ final String streamName = Notificator.createStreamNameFromUri(identifier);
+
+ final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+ Preconditions.checkNotNull(listener, "Listener doesn't exist : " + streamName);
+
+ listener.setQueryParams(notificationQueryParams.getStart(), notificationQueryParams.getStop(),
+ notificationQueryParams.getFilter(), false);
+ listener.setCloseVars(handlersHolder.getTransactionChainHandler(), handlersHolder.getSchemaHandler());
+
+ registration(ds, scope, listener, handlersHolder.getDomDataBrokerHandler().get());
+
+ final URI uri = prepareUriByStreamName(uriInfo, streamName);
+
+ final DOMDataReadWriteTransaction wTx =
+ handlersHolder.getTransactionChainHandler().get().newReadWriteTransaction();
+ final SchemaContext schemaContext = handlersHolder.getSchemaHandler().get();
+ final boolean exist = checkExist(schemaContext, wTx);
+
+ final NormalizedNode mapToStreams = RestconfMappingNodeUtil
+ .mapDataChangeNotificationStreamByIetfRestconfMonitoring(listener.getPath(),
+ notificationQueryParams.getStart(), listener.getOutputType(), uri,
+ getMonitoringModule(schemaContext), exist, schemaContext);
+ writeDataToDS(schemaContext, listener.getPath().getLastPathArgument().getNodeType().getLocalName(), wTx, exist,
+ mapToStreams);
+ submitData(wTx);
+ return uri;
+ }
+
+ public static Module getMonitoringModule(final SchemaContext schemaContext) {
+ final Module monitoringModule =
+ schemaContext.findModuleByNamespaceAndRevision(MonitoringModule.URI_MODULE, MonitoringModule.DATE);
+ return monitoringModule;
+ }
+
+ /**
+ * Parse input of query parameters - start-time or stop-time - from
+ * {@link DateAndTime} format to {@link Instant} format.
+ *
+ * @param entry
+ * start-time or stop-time as string in {@link DateAndTime}
+ * format
+ * @return parsed {@link Instant} by entry
+ */
+ public 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);
+
+ }
+
+ @SuppressWarnings("rawtypes")
+ static void writeDataToDS(final SchemaContext schemaContext,
+ final String name, final DOMDataReadWriteTransaction readWriteTransaction,
+ final boolean exist, final NormalizedNode mapToStreams) {
+ String pathId = "";
+ if (exist) {
+ pathId = MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + name;
+ } else {
+ pathId = MonitoringModule.PATH_TO_STREAMS;
+ }
+ readWriteTransaction.merge(LogicalDatastoreType.OPERATIONAL, IdentifierCodec.deserialize(pathId, schemaContext),
+ mapToStreams);
+ }
+
+ static void submitData(final DOMDataReadWriteTransaction readWriteTransaction) {
+ try {
+ readWriteTransaction.submit().checkedGet();
+ } catch (final TransactionCommitFailedException e) {
+ throw new RestconfDocumentedException("Problem while putting data to DS.", e);
+ }
+ }
+
+ /**
+ * Prepare map of values from URI.
+ *
+ * @param identifier
+ * URI
+ * @return {@link Map}
+ */
+ public static Map<String, String> mapValuesFromUri(final String identifier) {
+ final HashMap<String, String> result = new HashMap<>();
+ for (final String token : RestconfConstants.SLASH_SPLITTER.split(identifier)) {
+ final String[] paramToken = token.split(String.valueOf(RestconfStreamsConstants.EQUAL));
+ if (paramToken.length == 2) {
+ result.put(paramToken[0], paramToken[1]);
+ }
+ }
+ return result;
+ }
+
+ static URI prepareUriByStreamName(final UriInfo uriInfo, final String streamName) {
+ final int port = SubscribeToStreamUtil.prepareNotificationPort();
+
+ final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+ final UriBuilder uriToWebSocketServer =
+ uriBuilder.port(port).scheme(RestconfStreamsConstants.SCHEMA_SUBSCIBRE_URI);
+ final URI uri = uriToWebSocketServer.replacePath(streamName).build();
+ return uri;
+ }
+
+ /**
+ * Register data change listener in dom data broker and set it to listener
+ * on stream.
+ *
+ * @param ds
+ * {@link LogicalDatastoreType}
+ * @param scope
+ * {@link DataChangeScope}
+ * @param listener
+ * listener on specific stream
+ * @param domDataBroker
+ * data broker for register data change listener
+ */
+ @SuppressWarnings("deprecation")
+ private static void registration(final LogicalDatastoreType ds, final DataChangeScope scope,
+ final ListenerAdapter listener, final DOMDataBroker domDataBroker) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final YangInstanceIdentifier path = listener.getPath();
+ final ListenerRegistration<DOMDataChangeListener> registration =
+ domDataBroker.registerDataChangeListener(ds, path, listener, scope);
+
+ listener.setRegistration(registration);
+ }
+
+ /**
+ * Get port from web socket server. If doesn't exit, create it.
+ *
+ * @return port
+ */
+ private static int prepareNotificationPort() {
+ int port = RestconfStreamsConstants.NOTIFICATION_PORT;
+ try {
+ final WebSocketServer webSocketServer = WebSocketServer.getInstance();
+ port = webSocketServer.getPort();
+ } catch (final NullPointerException e) {
+ WebSocketServer.createInstance(RestconfStreamsConstants.NOTIFICATION_PORT);
+ }
+ return port;
+ }
+
+ static boolean checkExist(final SchemaContext schemaContext,
+ final DOMDataReadWriteTransaction readWriteTransaction) {
+ boolean exist;
+ try {
+ exist = readWriteTransaction.exists(LogicalDatastoreType.OPERATIONAL,
+ IdentifierCodec.deserialize(MonitoringModule.PATH_TO_STREAMS, schemaContext)).checkedGet();
+ } catch (final ReadFailedException e1) {
+ throw new RestconfDocumentedException("Problem while checking data if exists", e1);
+ }
+ return exist;
+ }
+
+ private static void registerToListenNotification(final NotificationListenerAdapter listener,
+ final NotificationServiceHandler notificationServiceHandler) {
+ if (listener.isListening()) {
+ return;
+ }
+
+ final SchemaPath path = listener.getSchemaPath();
+ final ListenerRegistration<DOMNotificationListener> registration =
+ notificationServiceHandler.get().registerNotificationListener(listener, path);
+
+ listener.setRegistration(registration);
+ }
+
+ /**
+ * Parse enum from URI.
+ *
+ * @param clazz
+ * enum type
+ * @param value
+ * string of enum value
+ * @return enum
+ */
+ private static <T> T parseURIEnum(final Class<T> clazz, final String value) {
+ if ((value == null) || value.equals("")) {
+ return null;
+ }
+ return ResolveEnumUtil.resolveEnum(clazz, value);
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.RestConnectorProvider;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util class for common methods of transactions.
+ *
+ */
+public final class TransactionUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
+
+ private TransactionUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Merged parents of data.
+ *
+ * @param path
+ * path of data
+ * @param schemaContext
+ * {@link SchemaContext}
+ * @param writeTx
+ * write transaction
+ */
+ public static void ensureParentsByMerge(final YangInstanceIdentifier path, final SchemaContext schemaContext,
+ final DOMDataWriteTransaction writeTx) {
+ final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
+ YangInstanceIdentifier rootNormalizedPath = null;
+
+ final Iterator<PathArgument> it = path.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;
+ }
+
+ Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
+
+ final NormalizedNode<?, ?> parentStructure = ImmutableNodes.fromInstanceId(schemaContext,
+ YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
+ writeTx.merge(LogicalDatastoreType.CONFIGURATION, rootNormalizedPath, parentStructure);
+ }
+
+ /**
+ * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+ * data does NOT already exists.
+ * @param transactionChain Transaction chain
+ * @param rwTransaction Transaction
+ * @param store Datastore
+ * @param path Path to be checked
+ * @param operationType Type of operation (READ, POST, PUT, DELETE...)
+ */
+ public static void checkItemExists(final DOMTransactionChain transactionChain,
+ final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final String operationType) {
+ final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
+ final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+ FutureCallbackTx.addCallback(future, operationType, response);
+
+ if (!response.result) {
+ // close transaction and reset transaction chain
+ rwTransaction.cancel();
+ RestConnectorProvider.resetTransactionChainForAdapaters(transactionChain);
+
+ // throw error
+ final String errMsg = "Operation via Restconf was not executed because data does not exist";
+ LOG.trace("{}:{}", errMsg, path);
+ throw new RestconfDocumentedException(
+ "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
+ }
+ }
+
+ /**
+ * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+ * data already exists.
+ * @param transactionChain Transaction chain
+ * @param rwTransaction Transaction
+ * @param store Datastore
+ * @param path Path to be checked
+ * @param operationType Type of operation (READ, POST, PUT, DELETE...)
+ */
+ public static void checkItemDoesNotExists(final DOMTransactionChain transactionChain,
+ final DOMDataReadWriteTransaction rwTransaction,
+ final LogicalDatastoreType store, final YangInstanceIdentifier path,
+ final String operationType) {
+ final CheckedFuture<Boolean, ReadFailedException> future = rwTransaction.exists(store, path);
+ final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+ FutureCallbackTx.addCallback(future, operationType, response);
+
+ if (response.result) {
+ // close transaction and reset transaction chain
+ rwTransaction.cancel();
+ RestConnectorProvider.resetTransactionChainForAdapaters(transactionChain);
+
+ // throw error
+ final String errMsg = "Operation via Restconf was not executed because data already exists";
+ LOG.trace("{}:{}", errMsg, path);
+ throw new RestconfDocumentedException(
+ "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
+ }
+ }
+}
* 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.restconf.nb.rfc8040.simple.services.api;
+package org.opendaylight.restconf.nb.rfc8040.services.simple.api;
/**
* Wrapper for all base services.
* 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.restconf.nb.rfc8040.simple.services.api;
+package org.opendaylight.restconf.nb.rfc8040.services.simple.api;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
* 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.restconf.nb.rfc8040.simple.services.api;
+package org.opendaylight.restconf.nb.rfc8040.services.simple.api;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
* 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.restconf.nb.rfc8040.simple.services.api;
+package org.opendaylight.restconf.nb.rfc8040.services.simple.api;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.Status;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+
+/**
+ * Special case only use by GET restconf/operations (since moment of old Yang
+ * parser and old yang model API removal) to build and use fake container for
+ * module.
+ */
+class FakeContainerSchemaNode implements ContainerSchemaNode {
+ static final SchemaPath PATH =
+ SchemaPath.create(true, QName.create(FakeRestconfModule.QNAME, "operations").intern());
+
+ private final Collection<DataSchemaNode> children;
+
+ FakeContainerSchemaNode(final Collection<LeafSchemaNode> children) {
+ this.children = ImmutableList.copyOf(children);
+ }
+
+ @Override
+ public Set<TypeDefinition<?>> getTypeDefinitions() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Collection<DataSchemaNode> getChildNodes() {
+ return this.children;
+ }
+
+ @Override
+ public Set<GroupingDefinition> getGroupings() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public DataSchemaNode getDataChildByName(final QName name) {
+ for (final DataSchemaNode node : this.children) {
+ if (node.getQName().equals(name)) {
+ return node;
+ }
+ }
+ throw new RestconfDocumentedException(name + " is not in child of " + PATH.getLastComponent());
+ }
+
+ @Override
+ public Set<UsesNode> getUses() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Set<AugmentationSchema> getAvailableAugmentations() {
+ return new HashSet<>();
+ }
+
+ @Override
+ public boolean isAugmenting() {
+ return false;
+ }
+
+ @Override
+ public boolean isAddedByUses() {
+ return false;
+ }
+
+ @Override
+ public boolean isConfiguration() {
+ return false;
+ }
+
+ @Override
+ public ConstraintDefinition getConstraints() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public QName getQName() {
+ return PATH.getLastComponent();
+ }
+
+ @Override
+ public SchemaPath getPath() {
+ return PATH;
+ }
+
+ @Override
+ public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public String getReference() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Status getStatus() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public boolean isPresenceContainer() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ForwardingObject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Deviation;
+import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+
+final class FakeImportedModule extends ForwardingObject implements Module {
+
+ private final Module delegate;
+
+ FakeImportedModule(final Module delegate) {
+ this.delegate = Preconditions.checkNotNull(delegate);
+ }
+
+ @Override
+ protected Module delegate() {
+ return delegate;
+ }
+
+ @Override
+ public Set<TypeDefinition<?>> getTypeDefinitions() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Collection<DataSchemaNode> getChildNodes() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public Set<GroupingDefinition> getGroupings() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public DataSchemaNode getDataChildByName(final QName name) {
+ return null;
+ }
+
+ @Override
+ public Set<UsesNode> getUses() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public String getModuleSourcePath() {
+ return null;
+ }
+
+ @Override
+ public QNameModule getQNameModule() {
+ return delegate.getQNameModule();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public URI getNamespace() {
+ return delegate.getNamespace();
+ }
+
+ @Override
+ public Date getRevision() {
+ return delegate.getRevision();
+ }
+
+ @Override
+ public String getPrefix() {
+ return delegate.getPrefix();
+ }
+
+ @Override
+ public String getYangVersion() {
+ return delegate.getYangVersion();
+ }
+
+ @Override
+ public String getDescription() {
+ return delegate.getDescription();
+ }
+
+ @Override
+ public String getReference() {
+ return delegate.getReference();
+ }
+
+ @Override
+ public String getOrganization() {
+ return delegate.getOrganization();
+ }
+
+ @Override
+ public String getContact() {
+ return delegate.getContact();
+ }
+
+ @Override
+ public Set<ModuleImport> getImports() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<Module> getSubmodules() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<FeatureDefinition> getFeatures() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<AugmentationSchema> getAugmentations() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<RpcDefinition> getRpcs() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<Deviation> getDeviations() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<IdentitySchemaNode> getIdentities() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public List<ExtensionDefinition> getExtensionSchemaNodes() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public String getSource() {
+ return null;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import java.util.List;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.Status;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
+
+/**
+ * Special case only use by GET restconf/operations (since moment of old Yang
+ * parser and old yang model API removal) to build and use fake leaf like child
+ * in container.
+ */
+final class FakeLeafSchemaNode implements LeafSchemaNode {
+
+ private final SchemaPath path;
+
+ /**
+ * Base values for fake leaf schema node.
+ *
+ * @param qname
+ * qname
+ */
+ FakeLeafSchemaNode(final QName qname) {
+ this.path = FakeContainerSchemaNode.PATH.createChild(qname);
+ }
+
+ @Override
+ public boolean isAugmenting() {
+ return true;
+ }
+
+ @Override
+ public boolean isAddedByUses() {
+ return false;
+ }
+
+ @Override
+ public boolean isConfiguration() {
+ return false;
+ }
+
+ @Override
+ public ConstraintDefinition getConstraints() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public QName getQName() {
+ return path.getLastComponent();
+ }
+
+ @Override
+ public SchemaPath getPath() {
+ return path;
+ }
+
+ @Override
+ public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException("Not supported operations.");
+ }
+
+ @Override
+ public String getReference() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public Status getStatus() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public TypeDefinition<?> getType() {
+ return BaseTypes.emptyType();
+ }
+
+ @Override
+ public String getDefault() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ @Override
+ public String getUnits() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import com.google.common.base.Preconditions;
+import java.util.Date;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+
+/**
+ * Fake {@link ModuleImport} implementation used to attach corrent prefix mapping to fake RPCs.
+ *
+ * @author Robert Varga
+ */
+final class FakeModuleImport implements ModuleImport {
+ private final Module module;
+
+ FakeModuleImport(final Module module) {
+ this.module = Preconditions.checkNotNull(module);
+ }
+
+ @Override
+ public String getModuleName() {
+ return module.getName();
+ }
+
+ @Override
+ public Date getRevision() {
+ return module.getRevision();
+ }
+
+ @Override
+ public String getPrefix() {
+ return module.getName();
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Deviation;
+import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+
+/**
+ * Special case only use by GET restconf/operations (since moment of old Yang
+ * parser and old yang model API removal) to build and use fake module to create
+ * new schema context.
+ */
+final class FakeRestconfModule implements Module {
+
+ static final QNameModule QNAME;
+
+ static {
+ Date date;
+ try {
+ date = SimpleDateFormatUtil.getRevisionFormat().parse("2016-06-28");
+ } catch (final ParseException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ QNAME = QNameModule.create(URI.create("urn:ietf:params:xml:ns:yang:ietf-restconf"), date).intern();
+ }
+
+ private final Collection<DataSchemaNode> children;
+ private final ImmutableSet<ModuleImport> imports;
+
+ /**
+ * Instantiate a new fake module.
+ *
+ * @param neededModules needed import statements
+ * @param child fake child container
+ */
+ FakeRestconfModule(final Collection<Module> neededModules, final ContainerSchemaNode child) {
+ this.children = ImmutableList.of(child);
+ this.imports = ImmutableSet.copyOf(Collections2.transform(neededModules, FakeModuleImport::new));
+ }
+
+ @Override
+ public Set<TypeDefinition<?>> getTypeDefinitions() {
+ throw new UnsupportedOperationException("Not supported operations.");
+ }
+
+ @Override
+ public Collection<DataSchemaNode> getChildNodes() {
+ return this.children;
+ }
+
+ @Override
+ public Set<GroupingDefinition> getGroupings() {
+ throw new UnsupportedOperationException("Not supported operations.");
+ }
+
+ @Override
+ public DataSchemaNode getDataChildByName(final QName name) {
+ for (final DataSchemaNode node : this.children) {
+ if (node.getQName().equals(name)) {
+ return node;
+ }
+ }
+ throw new RestconfDocumentedException(name + " is not in child of " + FakeRestconfModule.QNAME);
+ }
+
+ @Override
+ public Set<UsesNode> getUses() {
+ throw new UnsupportedOperationException("Not supported operations.");
+ }
+
+ @Override
+ public String getModuleSourcePath() {
+ throw new UnsupportedOperationException("Not supported operations.");
+ }
+
+ @Override
+ public QNameModule getQNameModule() {
+ return QNAME;
+ }
+
+ @Override
+ public String getName() {
+ return "ietf-restconf";
+ }
+
+ @Override
+ public URI getNamespace() {
+ return QNAME.getNamespace();
+ }
+
+ @Override
+ public Date getRevision() {
+ return QNAME.getRevision();
+ }
+
+ @Override
+ public String getPrefix() {
+ return "restconf";
+ }
+
+ @Override
+ public String getYangVersion() {
+ return YangVersion.VERSION_1.toString();
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public String getReference() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public String getOrganization() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public String getContact() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<ModuleImport> getImports() {
+ return imports;
+ }
+
+ @Override
+ public Set<Module> getSubmodules() {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set<FeatureDefinition> getFeatures() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<NotificationDefinition> getNotifications() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<AugmentationSchema> getAugmentations() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<RpcDefinition> getRpcs() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<Deviation> getDeviations() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public Set<IdentitySchemaNode> getIdentities() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public List<ExtensionDefinition> getExtensionSchemaNodes() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+
+ @Override
+ public String getSource() {
+ throw new UnsupportedOperationException("Operation not implemented.");
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.RestconfModule;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfService;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+public class RestconfImpl implements RestconfService {
+
+ private final SchemaContextHandler schemaContextHandler;
+
+ public RestconfImpl(final SchemaContextHandler schemaContextHandler) {
+ this.schemaContextHandler = schemaContextHandler;
+ }
+
+ @Override
+ public NormalizedNodeContext getLibraryVersion() {
+ final SchemaContext context = this.schemaContextHandler.get();
+ SchemaNode schemaNode = null;
+ for (final GroupingDefinition groupingDefinition : context
+ .findModuleByNamespaceAndRevision(RestconfModule.URI_MODULE, RestconfModule.DATE).getGroupings()) {
+ if (groupingDefinition.getQName().equals(RestconfModule.RESTCONF_GROUPING_QNAME)) {
+ schemaNode = ((ContainerSchemaNode) groupingDefinition
+ .getDataChildByName(RestconfModule.RESTCONF_CONTAINER_QNAME))
+ .getDataChildByName(RestconfModule.LIB_VER_LEAF_QNAME);
+ }
+ }
+ final YangInstanceIdentifier yangIId = YangInstanceIdentifier.of(
+ QName.create(RestconfModule.NAME, RestconfModule.REVISION, RestconfModule.LIB_VER_LEAF_SCHEMA_NODE));
+ final InstanceIdentifierContext<? extends SchemaNode> iid =
+ new InstanceIdentifierContext<SchemaNode>(yangIId, schemaNode, null, context);
+ final NormalizedNode<?, ?> data =
+ Builders.leafBuilder((LeafSchemaNode) schemaNode).withValue(IetfYangLibrary.REVISION).build();
+ return new NormalizedNodeContext(iid, data);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfOperationsService;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.util.SimpleSchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link RestconfOperationsService}.
+ *
+ */
+public class RestconfOperationsServiceImpl implements RestconfOperationsService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestconfOperationsServiceImpl.class);
+
+ private final SchemaContextHandler schemaContextHandler;
+ private final DOMMountPointServiceHandler domMountPointServiceHandler;
+
+ /**
+ * Set {@link SchemaContextHandler} for getting actual {@link SchemaContext}.
+ *
+ * @param schemaContextHandler
+ * handling schema context
+ * @param domMountPointServiceHandler
+ * handling dom mount point service
+ */
+ public RestconfOperationsServiceImpl(final SchemaContextHandler schemaContextHandler,
+ final DOMMountPointServiceHandler domMountPointServiceHandler) {
+ this.schemaContextHandler = schemaContextHandler;
+ this.domMountPointServiceHandler = domMountPointServiceHandler;
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final UriInfo uriInfo) {
+ final SchemaContextRef ref = new SchemaContextRef(this.schemaContextHandler.get());
+ return getOperations(ref.getModules(), null);
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ final Set<Module> modules;
+ final DOMMountPoint mountPoint;
+ final SchemaContextRef ref = new SchemaContextRef(this.schemaContextHandler.get());
+ if (identifier.contains(RestconfConstants.MOUNT)) {
+ final InstanceIdentifierContext<?> mountPointIdentifier = ParserIdentifier.toInstanceIdentifier(identifier,
+ ref.get(), Optional.of(this.domMountPointServiceHandler.get()));
+ mountPoint = mountPointIdentifier.getMountPoint();
+ modules = ref.getModules(mountPoint);
+ } else {
+ final String errMsg =
+ "URI has bad format. If operations behind mount point should be showed, URI has to end with ";
+ LOG.debug(errMsg + RestconfConstants.MOUNT + " for " + identifier);
+ throw new RestconfDocumentedException(errMsg + RestconfConstants.MOUNT, ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
+ return getOperations(modules, mountPoint);
+ }
+
+ /**
+ * Special case only for GET restconf/operations use (since moment of old
+ * Yang parser and old Yang model API removal). The method is creating fake
+ * schema context with fake module and fake data by use own implementations
+ * of schema nodes and module.
+ *
+ * @param modules
+ * set of modules for get RPCs from every module
+ * @param mountPoint
+ * mount point, if in use otherwise null
+ * @return {@link NormalizedNodeContext}
+ */
+ private static NormalizedNodeContext getOperations(final Set<Module> modules, final DOMMountPoint mountPoint) {
+ final Collection<Module> neededModules = new ArrayList<>(modules.size());
+ final ArrayList<LeafSchemaNode> fakeRpcSchema = new ArrayList<>();
+
+ for (final Module m : modules) {
+ final Set<RpcDefinition> rpcs = m.getRpcs();
+ if (!rpcs.isEmpty()) {
+ neededModules.add(m);
+
+ fakeRpcSchema.ensureCapacity(fakeRpcSchema.size() + rpcs.size());
+ rpcs.forEach(rpc -> fakeRpcSchema.add(new FakeLeafSchemaNode(rpc.getQName())));
+ }
+ }
+
+ final ContainerSchemaNode fakeCont = new FakeContainerSchemaNode(fakeRpcSchema);
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder =
+ Builders.containerBuilder(fakeCont);
+
+ for (final LeafSchemaNode leaf : fakeRpcSchema) {
+ containerBuilder.withChild(Builders.leafBuilder(leaf).build());
+ }
+
+ final Collection<Module> fakeModules = new ArrayList<>(neededModules.size() + 1);
+ neededModules.forEach(imp -> fakeModules.add(new FakeImportedModule(imp)));
+ fakeModules.add(new FakeRestconfModule(neededModules, fakeCont));
+
+ final SchemaContext fakeSchemaCtx = SimpleSchemaContext.forModules(ImmutableSet.copyOf(fakeModules));
+ final InstanceIdentifierContext<ContainerSchemaNode> instanceIdentifierContext =
+ new InstanceIdentifierContext<>(null, fakeCont, mountPoint, fakeSchemaCtx);
+ return new NormalizedNodeContext(instanceIdentifierContext, containerBuilder.build());
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.simple.impl;
+
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfSchemaService;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Implementation of {@link RestconfSchemaService}.
+ *
+ */
+public class RestconfSchemaServiceImpl implements RestconfSchemaService {
+
+ private final SchemaContextHandler schemaContextHandler;
+ private final DOMMountPointServiceHandler domMountPointServiceHandler;
+
+ /**
+ * Set {@link SchemaContextHandler} for getting actual {@link SchemaContext}
+ * .
+ *
+ * @param schemaContextHandler
+ * handling schema context
+ * @param domMountPointServiceHandler
+ * handling dom mount point service
+ */
+ public RestconfSchemaServiceImpl(final SchemaContextHandler schemaContextHandler,
+ final DOMMountPointServiceHandler domMountPointServiceHandler) {
+ this.schemaContextHandler = schemaContextHandler;
+ this.domMountPointServiceHandler = domMountPointServiceHandler;
+ }
+
+ @Override
+ public SchemaExportContext getSchema(final String identifier) {
+ final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
+ return ParserIdentifier.toSchemaExportContextFromIdentifier(schemaContextRef.get(), identifier,
+ this.domMountPointServiceHandler.get());
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.services.wrapper;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMDataBrokerHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.NotificationServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.RpcServiceHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfDataService;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfInvokeOperationsService;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.api.TransactionServicesWrapper;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.RestconfDataServiceImpl;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.RestconfInvokeOperationsServiceImpl;
+import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.RestconfStreamsSubscriptionServiceImpl;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.BaseServicesWrapper;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfOperationsService;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfSchemaService;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.api.RestconfService;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.impl.RestconfImpl;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.impl.RestconfOperationsServiceImpl;
+import org.opendaylight.restconf.nb.rfc8040.services.simple.impl.RestconfSchemaServiceImpl;
+
+/**
+ * Wrapper for services.
+ * <ul>
+ * <li>{@link BaseServicesWrapper}
+ * <li>{@link TransactionServicesWrapper}
+ * </ul>
+ *
+ */
+@Path("/")
+public class ServicesWrapperImpl implements BaseServicesWrapper, TransactionServicesWrapper {
+
+ private RestconfDataService delegRestconfDataService;
+ private RestconfInvokeOperationsService delegRestconfInvokeOpsService;
+ private RestconfStreamsSubscriptionService delegRestconfSubscrService;
+ private RestconfOperationsService delegRestOpsService;
+ private RestconfSchemaService delegRestSchService;
+ private RestconfService delegRestService;
+
+ private ServicesWrapperImpl() {
+ }
+
+ private static class InstanceHolder {
+ public static final ServicesWrapperImpl INSTANCE = new ServicesWrapperImpl();
+ }
+
+ public static ServicesWrapperImpl getInstance() {
+ return InstanceHolder.INSTANCE;
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final UriInfo uriInfo) {
+ return this.delegRestOpsService.getOperations(uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
+ return this.delegRestOpsService.getOperations(identifier, uriInfo);
+ }
+
+ @Override
+ public SchemaExportContext getSchema(final String mountAndModuleId) {
+ return this.delegRestSchService.getSchema(mountAndModuleId);
+ }
+
+ @Override
+ public Response readData(final UriInfo uriInfo) {
+ return this.delegRestconfDataService.readData(uriInfo);
+ }
+
+ @Override
+ public Response readData(final String identifier, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.readData(identifier, uriInfo);
+ }
+
+ @Override
+ public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.putData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.postData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.postData(payload, uriInfo);
+ }
+
+ @Override
+ public Response deleteData(final String identifier) {
+ return this.delegRestconfDataService.deleteData(identifier);
+ }
+
+ @Override
+ public PatchStatusContext patchData(final String identifier, final PatchContext context, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.patchData(identifier, context, uriInfo);
+ }
+
+ @Override
+ public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.patchData(context, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ return this.delegRestconfInvokeOpsService.invokeRpc(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
+ return this.delegRestconfSubscrService.subscribeToStream(identifier, uriInfo);
+ }
+
+ @Override
+ public NormalizedNodeContext getLibraryVersion() {
+ return this.delegRestService.getLibraryVersion();
+ }
+
+ public void setHandlers(final SchemaContextHandler schemaCtxHandler,
+ final DOMMountPointServiceHandler domMountPointServiceHandler,
+ final TransactionChainHandler transactionChainHandler, final DOMDataBrokerHandler domDataBrokerHandler,
+ final RpcServiceHandler rpcServiceHandler, final NotificationServiceHandler notificationServiceHandler) {
+ this.delegRestOpsService = new RestconfOperationsServiceImpl(schemaCtxHandler, domMountPointServiceHandler);
+ this.delegRestSchService = new RestconfSchemaServiceImpl(schemaCtxHandler, domMountPointServiceHandler);
+ this.delegRestconfSubscrService = new RestconfStreamsSubscriptionServiceImpl(domDataBrokerHandler,
+ notificationServiceHandler, schemaCtxHandler, transactionChainHandler);
+ this.delegRestconfDataService =
+ new RestconfDataServiceImpl(schemaCtxHandler, transactionChainHandler, domMountPointServiceHandler,
+ this.delegRestconfSubscrService);
+ this.delegRestconfInvokeOpsService =
+ new RestconfInvokeOperationsServiceImpl(rpcServiceHandler, schemaCtxHandler);
+ this.delegRestService = new RestconfImpl(schemaCtxHandler);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+import io.netty.channel.Channel;
+import io.netty.util.internal.ConcurrentSet;
+import java.util.Set;
+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 = new ConcurrentSet<>();
+ 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() throws Exception {
+ this.registration.close();
+ this.registration = null;
+
+ deleteDataInDS();
+ 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);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule;
+import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+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();
+
+ private TransactionChainHandler transactionChainHandler;
+ protected SchemaContextHandler schemaHandler;
+ private String localName;
+
+ /**
+ * Transaction chain for delete data in DS on close().
+ *
+ * @param transactionChainHandler
+ * creating new write transaction for delete data on close
+ * @param schemaHandler
+ * for getting schema to deserialize
+ * {@link MonitoringModule#PATH_TO_STREAM_WITHOUT_KEY} to
+ * {@link YangInstanceIdentifier}
+ */
+ public void setCloseVars(final TransactionChainHandler transactionChainHandler,
+ final SchemaContextHandler schemaHandler) {
+ this.transactionChainHandler = transactionChainHandler;
+ this.schemaHandler = schemaHandler;
+ }
+
+ /**
+ * Delete data in DS.
+ */
+ protected void deleteDataInDS() throws Exception {
+ final DOMDataWriteTransaction wTx = this.transactionChainHandler.get().newWriteOnlyTransaction();
+ wTx.delete(LogicalDatastoreType.OPERATIONAL, IdentifierCodec
+ .deserialize(MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + this.localName, this.schemaHandler.get()));
+ wTx.submit().checkedGet();
+ }
+
+ /**
+ * Set localName of last path element of specific listener.
+ *
+ * @param localName
+ * local name
+ */
+ protected void setLocalNameOfPath(final String localName) {
+ this.localName = localName;
+ }
+
+ /**
+ * 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 context
+ * actual schema context
+ * @param schemaPath
+ * schema path of data
+ * @return {@link DOMResult}
+ */
+ protected DOMResult writeNormalizedNode(final NormalizedNode<?, ?> normalized, final SchemaContext context,
+ final SchemaPath schemaPath) 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, context, schemaPath);
+ 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);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+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;
+
+ @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
+ */
+ public void setQueryParams(final Instant start, final Optional<Instant> stop, final Optional<String> filter,
+ final boolean leafNodesOnly) {
+ this.start = Preconditions.checkNotNull(start);
+ this.stop = stop.orElse(null);
+ this.filter = filter.orElse(null);
+ this.leafNodesOnly = leafNodesOnly;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Checking query parameters on specific notification.
+ *
+ * @param xml data of notification
+ * @param listener listener of notification
+ * @return true if notification meets the requirements of query parameters,
+ * false otherwise
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ protected <T extends BaseListenerInterface> boolean checkQueryParams(final String xml, final T listener) {
+ final Instant now = Instant.now();
+ if (this.stop != null) {
+ if ((this.start.compareTo(now) < 0) && (this.stop.compareTo(now) > 0)) {
+ return checkFilter(xml);
+ }
+ 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 checkFilter(xml);
+ }
+ } else {
+ return checkFilter(xml);
+ }
+ return false;
+ }
+
+ /**
+ * Check if is filter used and then prepare and post data do client.
+ *
+ * @param xml data of notification
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ private 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);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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();
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+/**
+ * Type of the event.
+ */
+enum EventType {
+ REGISTER, DEREGISTER, NOTIFY;
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMResult;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+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.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+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 DOMDataChangeListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class);
+
+ private final YangInstanceIdentifier path;
+ private final String streamName;
+ private final NotificationOutputType outputType;
+
+ private AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change;
+
+ /**
+ * Creates new {@link ListenerAdapter} listener specified by path and stream
+ * name and register for subscribing.
+ *
+ * @param path
+ * Path to data in data store.
+ * @param streamName
+ * The name of the stream.
+ * @param outputType
+ * Type of output on notification (JSON, XML)
+ */
+ ListenerAdapter(final YangInstanceIdentifier path, final String streamName,
+ final NotificationOutputType outputType) {
+ super();
+ register(this);
+ setLocalNameOfPath(path.getLastPathArgument().getNodeType().getLocalName());
+
+ this.outputType = Preconditions.checkNotNull(outputType);
+ this.path = Preconditions.checkNotNull(path);
+ Preconditions.checkArgument((streamName != null) && !streamName.isEmpty());
+ this.streamName = streamName;
+ }
+
+ @Override
+ public void onDataChanged(final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change) {
+ this.change = change;
+ final String xml = prepareXml();
+ if (checkQueryParams(xml, this)) {
+ prepareAndPostData(xml);
+ }
+ }
+
+ /**
+ * Gets the name of the stream.
+ *
+ * @return The name of the stream.
+ */
+ @Override
+ public String getStreamName() {
+ return this.streamName;
+ }
+
+ @Override
+ public String getOutputType() {
+ return this.outputType.getName();
+ }
+
+ /**
+ * Get path pointed to data in data store.
+ *
+ * @return Path pointed to data in data store.
+ */
+ public YangInstanceIdentifier getPath() {
+ return this.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 (this.outputType.equals(NotificationOutputType.JSON)) {
+ try {
+ final JsonNode node = new XmlMapper().readTree(xml.getBytes());
+ event.setData(node.toString());
+ } catch (final IOException e) {
+ LOG.error("Error parsing XML {}", xml, e);
+ Throwables.propagate(e);
+ }
+ } 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 SchemaContext schemaContext = schemaHandler.get();
+ 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, this.change,
+ 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 change
+ * {@link AsyncDataChangeEvent}
+ */
+ private void addValuesToDataChangedNotificationEventElement(final Document doc,
+ final Element dataChangedNotificationEventElement,
+ final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change,
+ final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
+
+ addCreatedChangedValuesFromDataToElement(doc, change.getCreatedData().entrySet(),
+ dataChangedNotificationEventElement, Operation.CREATED, schemaContext, dataSchemaContextTree);
+
+ addCreatedChangedValuesFromDataToElement(doc, change.getUpdatedData().entrySet(),
+ dataChangedNotificationEventElement, Operation.UPDATED, schemaContext, dataSchemaContextTree);
+
+ addValuesFromDataToElement(doc, change.getRemovedPaths(), dataChangedNotificationEventElement,
+ Operation.DELETED, schemaContext, dataSchemaContextTree);
+ }
+
+ /**
+ * Adds values from data to element.
+ *
+ * @param doc
+ * {@link Document}
+ * @param data
+ * Set of {@link YangInstanceIdentifier}.
+ * @param element
+ * {@link Element}
+ * @param operation
+ * {@link Operation}
+ * @param schemaContext
+ * @param dataSchemaContextTree
+ */
+ private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data,
+ final Element element, final Operation operation, final SchemaContext schemaContext,
+ final DataSchemaContextTree dataSchemaContextTree) {
+ if ((data == null) || data.isEmpty()) {
+ return;
+ }
+ for (final YangInstanceIdentifier path : data) {
+ if (!dataSchemaContextTree.getChild(path).isMixin()) {
+ final Node node = createDataChangeEventElement(doc, path, operation, schemaContext);
+ element.appendChild(node);
+ }
+ }
+ }
+
+ private void addCreatedChangedValuesFromDataToElement(final Document doc,
+ final Set<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> data, final Element element,
+ final Operation operation, final SchemaContext schemaContext,
+ final DataSchemaContextTree dataSchemaContextTree) {
+ if ((data == null) || data.isEmpty()) {
+ return;
+ }
+ for (final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry : data) {
+ if (!dataSchemaContextTree.getChild(entry.getKey()).isMixin()
+ && (!getLeafNodesOnly() || entry.getValue() instanceof LeafNode)) {
+ final Node node = createCreatedChangedDataChangeEventElement(doc, entry, operation, schemaContext,
+ dataSchemaContextTree);
+ element.appendChild(node);
+ }
+ }
+ }
+
+ /**
+ * Creates changed event element from data.
+ *
+ * @param doc
+ * {@link Document}
+ * @param path
+ * Path to data in data store.
+ * @param operation
+ * {@link Operation}
+ * @param schemaContext
+ * @return {@link Node} node represented by changed event element.
+ */
+ private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path,
+ final Operation operation, final SchemaContext schemaContext) {
+ final Element dataChangeEventElement = doc.createElement("data-change-event");
+ final Element pathElement = doc.createElement("path");
+ addPathAsValueToElement(path, pathElement, schemaContext);
+ dataChangeEventElement.appendChild(pathElement);
+
+ final Element operationElement = doc.createElement("operation");
+ operationElement.setTextContent(operation.value);
+ dataChangeEventElement.appendChild(operationElement);
+
+ return dataChangeEventElement;
+ }
+
+ private Node createCreatedChangedDataChangeEventElement(final Document doc,
+ final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry, final Operation operation,
+ final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
+ final Element dataChangeEventElement = doc.createElement("data-change-event");
+ final Element pathElement = doc.createElement("path");
+ final YangInstanceIdentifier path = entry.getKey();
+ addPathAsValueToElement(path, pathElement, schemaContext);
+ dataChangeEventElement.appendChild(pathElement);
+
+ final Element operationElement = doc.createElement("operation");
+ operationElement.setTextContent(operation.value);
+ dataChangeEventElement.appendChild(operationElement);
+
+ try {
+ SchemaPath nodePath;
+ final NormalizedNode<?, ?> normalized = entry.getValue();
+ if ((normalized instanceof MapEntryNode) || (normalized instanceof UnkeyedListEntryNode)) {
+ nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath();
+ } else {
+ nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath().getParent();
+ }
+ final DOMResult domResult = writeNormalizedNode(normalized, schemaContext, nodePath);
+ final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
+ final Element dataElement = doc.createElement("data");
+ dataElement.appendChild(result);
+ dataChangeEventElement.appendChild(dataElement);
+ } catch (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 path
+ * Path to data in data store.
+ * @param element
+ * {@link Element}
+ * @param schemaContext
+ */
+ @SuppressWarnings("rawtypes")
+ private void addPathAsValueToElement(final YangInstanceIdentifier path, final Element element,
+ final SchemaContext schemaContext) {
+ final StringBuilder textContent = new StringBuilder();
+
+ for (final PathArgument pathArgument : path.getPathArguments()) {
+ if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+ continue;
+ }
+ textContent.append("/");
+ writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType(), schemaContext);
+ if (pathArgument instanceof NodeIdentifierWithPredicates) {
+ final Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
+ for (final QName keyValue : predicates.keySet()) {
+ final String predicateValue = String.valueOf(predicates.get(keyValue));
+ textContent.append("[");
+ writeIdentifierWithNamespacePrefix(element, textContent, keyValue, schemaContext);
+ 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
+ * @param schemaContext
+ */
+ private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent,
+ final QName qualifiedName, final SchemaContext schemaContext) {
+ final Module module = schemaContext.findModuleByNamespaceAndRevision(qualifiedName.getNamespace(),
+ qualifiedName.getRevision());
+
+ 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+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 java.util.Collection;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.dom.DOMResult;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+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.gson.JSONCodecFactory;
+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.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+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 class NotificationListenerAdapter extends AbstractCommonSubscriber implements DOMNotificationListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NotificationListenerAdapter.class);
+
+ private final String streamName;
+ private final SchemaPath path;
+ private final String outputType;
+
+ private SchemaContext schemaContext;
+ private DOMNotification notification;
+
+ /**
+ * 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 SchemaPath path, final String streamName, final String outputType) {
+ super();
+ register(this);
+ setLocalNameOfPath(path.getLastComponent().getLocalName());
+
+ this.outputType = Preconditions.checkNotNull(outputType);
+ this.path = Preconditions.checkNotNull(path);
+ Preconditions.checkArgument(streamName != null && !streamName.isEmpty());
+ this.streamName = streamName;
+ }
+
+ /**
+ * Get outputType of listener.
+ *
+ * @return the outputType
+ */
+ @Override
+ public String getOutputType() {
+ return this.outputType;
+ }
+
+ @Override
+ public void onNotification(final DOMNotification notification) {
+ this.schemaContext = schemaHandler.get();
+ this.notification = notification;
+
+ final String xml = prepareXml();
+ if (checkQueryParams(xml, this)) {
+ prepareAndPostData(xml);
+ }
+ }
+
+ /**
+ * Get stream name of this listener.
+ *
+ * @return {@link String}
+ */
+ @Override
+ public String getStreamName() {
+ return this.streamName;
+ }
+
+ /**
+ * Get schema path of notification.
+ *
+ * @return {@link SchemaPath}
+ */
+ public SchemaPath getSchemaPath() {
+ return this.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 (this.outputType.equals("JSON")) {
+ event.setData(prepareJson());
+ } else {
+ event.setData(xml);
+ }
+ post(event);
+ }
+
+ /**
+ * Prepare json from notification data.
+ *
+ * @return json as {@link String}
+ */
+ @VisibleForTesting
+ String prepareJson() {
+ final JsonParser jsonParser = new JsonParser();
+ final JsonObject json = new JsonObject();
+ json.add("ietf-restconf:notification", jsonParser.parse(writeBodyToString()));
+ json.addProperty("event-time", ListenerAdapter.toRFC3339(Instant.now()));
+ return json.toString();
+ }
+
+ @VisibleForTesting
+ void setNotification(final DOMNotification notification) {
+ this.notification = Preconditions.checkNotNull(notification);
+ }
+
+ @VisibleForTesting
+ void setSchemaContext(final SchemaContext schemaContext) {
+ this.schemaContext = Preconditions.checkNotNull(schemaContext);
+ }
+
+ private String writeBodyToString() {
+ final Writer writer = new StringWriter();
+ final NormalizedNodeStreamWriter jsonStream =
+ JSONNormalizedNodeStreamWriter.createExclusiveWriter(JSONCodecFactory.getShared(this.schemaContext),
+ this.notification.getType(), null, JsonWriterFactory.createJsonWriter(writer));
+ final NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream);
+ try {
+ nodeWriter.write(this.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 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, this.notification, this.schemaContext);
+ notificationElement.appendChild(notificationEventElement);
+
+ return transformDoc(doc);
+ }
+
+ private void addValuesToNotificationEventElement(final Document doc, final Element element,
+ final DOMNotification notification, final SchemaContext schemaContext) {
+ if (notification == null) {
+ return;
+ }
+
+ final NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>> body =
+ notification.getBody();
+ try {
+
+ final DOMResult domResult = writeNormalizedNode(body, schemaContext, this.path);
+ 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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.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.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link Notificator} is responsible to create, remove and find
+ * {@link ListenerAdapter} listener.
+ */
+public 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 ListenerAdapter listener = new ListenerAdapter(path, streamName, outputType);
+ 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<SchemaPath> paths,
+ final String streamName, final String outputType) {
+ final List<NotificationListenerAdapter> listListeners = new ArrayList<>();
+ for (final SchemaPath path : paths) {
+ final NotificationListenerAdapter listener = new NotificationListenerAdapter(path, streamName, outputType);
+ 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);
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.websockets;
+
+import com.google.common.base.Preconditions;
+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.restconf.nb.rfc8040.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 class WebSocketServer implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class);
+
+ private static WebSocketServer instance = null;
+
+ private final int port;
+
+ private EventLoopGroup bossGroup;
+ private EventLoopGroup workerGroup;
+
+
+ private WebSocketServer(final int port) {
+ this.port = port;
+ }
+
+ /**
+ * Create singleton instance of {@link WebSocketServer}.
+ *
+ * @param port TCP port used for this server
+ * @return instance of {@link WebSocketServer}
+ */
+ public static WebSocketServer createInstance(final int port) {
+ Preconditions.checkState(instance == null, "createInstance() has already been called");
+ Preconditions.checkArgument(port >= 1024, "Privileged port (below 1024) is not allowed");
+
+ instance = new WebSocketServer(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() {
+ Preconditions.checkNotNull(instance, "createInstance() must be called prior to getInstance()");
+ return instance;
+ }
+
+ /**
+ * Destroy the existing instance.
+ */
+ public static void destroyInstance() {
+ Preconditions.checkState(instance != null, "createInstance() must be called prior to destroyInstance()");
+
+ instance.stop();
+ instance = null;
+ }
+
+ @Override
+ 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(port).sync().channel();
+ LOG.info("Web socket server started at port {}.", port);
+
+ channel.closeFuture().sync();
+ } catch (final InterruptedException e) {
+ LOG.error("Web socket server encountered an error during startup attempt on port {}", port, e);
+ } finally {
+ stop();
+ }
+ }
+
+ /**
+ * Stops the web socket server and removes all listeners.
+ */
+ private void stop() {
+ LOG.debug("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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.websockets;
+
+import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
+import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
+import static io.netty.handler.codec.http.HttpHeaders.Names.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.HttpVersion.HTTP_1_1;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+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.io.IOException;
+import java.util.List;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.restconf.nb.rfc8040.streams.listeners.Notificator;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+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) throws Exception {
+ 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) throws Exception {
+ // Handle a bad request.
+ if (!req.getDecoderResult().isSuccess()) {
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
+ return;
+ }
+
+ // Allow only GET methods.
+ if (req.getMethod() != GET) {
+ sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
+ return;
+ }
+
+ final String streamName = Notificator.createStreamNameFromUri(req.getUri());
+ if (streamName.contains(RestconfConstants.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(RestconfConstants.NOTIFICATION_STREAM)) {
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (!listeners.isEmpty() && (listeners != null)) {
+ 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).
+ if (res.getStatus().code() != 200) {
+ final ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
+ res.content().writeBytes(buf);
+ buf.release();
+ setContentLength(res, res.content().readableBytes());
+ }
+
+ // Send the response and close the connection if necessary.
+ final ChannelFuture f = ctx.channel().writeAndFlush(res);
+ if (!isKeepAlive(req) || (res.getStatus().code() != 200)) {
+ 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) throws IOException {
+ if (frame instanceof CloseWebSocketFrame) {
+ this.handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
+ final String streamName = Notificator.createStreamNameFromUri(((CloseWebSocketFrame) frame).reasonText());
+ if (streamName.contains(RestconfConstants.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(RestconfConstants.NOTIFICATION_STREAM)) {
+ final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+ if (!listeners.isEmpty() && (listeners != null)) {
+ for (final NotificationListenerAdapter listener : listeners) {
+ listener.removeSubscriber(ctx.channel());
+ }
+ }
+ }
+ return;
+ } else if (frame instanceof PingWebSocketFrame) {
+ ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
+ return;
+ }
+ }
+
+ @Override
+ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
+ if (!(cause instanceof java.nio.channels.ClosedChannelException)) {
+ // LOG.info("Not expected error cause: ", cause.toString());
+ }
+ 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.getUri();
+ }
+
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.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) throws Exception {
+ ChannelPipeline pipeline = ch.pipeline();
+ pipeline.addLast("codec-http", new HttpServerCodec());
+ pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
+ pipeline.addLast("handler", new WebSocketServerHandler());
+ }
+
+}
public static final String IDENTIFIER = "identifier";
public static final char SLASH = '/';
public static final Splitter SLASH_SPLITTER = Splitter.on(SLASH);
- public static final String DRAFT_PATTERN = "restconf/17";
+ public static final String DRAFT_PATTERN = "restconf/18";
+
+ public static final CharSequence DATA_SUBSCR = "data-change-event-subscription";
+ public static final CharSequence NOTIFICATION_STREAM = "notification-stream";
private RestconfConstants() {
throw new UnsupportedOperationException("Util class");
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.mapping;
+
+/**
+ * Util class for constants of mapping node.
+ *
+ */
+public final class RestconfMappingNodeConstants {
+
+ public static final String NAME = "name";
+ public static final String REVISION = "revision";
+ public static final String NAMESPACE = "namespace";
+ public static final String FEATURE = "feature";
+ public static final String DESCRIPTION = "description";
+ public static final String REPLAY_SUPPORT = "replay-support";
+ public static final String REPLAY_LOG = "replay-log-creation-time";
+ public static final String EVENTS = "events";
+
+ private RestconfMappingNodeConstants() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.mapping;
+
+import com.google.common.base.Optional;
+import java.net.URI;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Collection;
+import java.util.Set;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule;
+import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule.QueryParams;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160621.module.list.Module.ConformanceType;
+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.LeafSetEntryNode;
+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.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Deviation;
+import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
+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.NotificationDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Util class for mapping nodes.
+ *
+ */
+public final class RestconfMappingNodeUtil {
+
+ private RestconfMappingNodeUtil() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Map data from modules to {@link NormalizedNode}.
+ *
+ * @param modules
+ * modules for mapping
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @param context
+ * schema context
+ * @param moduleSetId
+ * module-set-id of actual set
+ * @return mapped data as {@link NormalizedNode}
+ */
+ public static NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>>
+ mapModulesByIetfYangLibraryYang(final Set<Module> modules, final Module ietfYangLibraryModule,
+ final SchemaContext context, final String moduleSetId) {
+ final DataSchemaNode modulesStateSch =
+ ietfYangLibraryModule.getDataChildByName(IetfYangLibrary.MODUELS_STATE_CONT_QNAME);
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> modulesStateBuilder =
+ Builders.containerBuilder((ContainerSchemaNode) modulesStateSch);
+
+ final DataSchemaNode moduleSetIdSch =
+ ((ContainerSchemaNode) modulesStateSch).getDataChildByName(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME);
+ modulesStateBuilder
+ .withChild(Builders.leafBuilder((LeafSchemaNode) moduleSetIdSch).withValue(moduleSetId).build());
+
+ final DataSchemaNode moduleSch = findNodeInGroupings(IetfYangLibrary.MODULE_QNAME_LIST, ietfYangLibraryModule);
+ final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder =
+ Builders.orderedMapBuilder((ListSchemaNode) moduleSch);
+ for (final Module module : context.getModules()) {
+ fillMapByModules(mapBuilder, moduleSch, false, module, ietfYangLibraryModule, context);
+ }
+ return modulesStateBuilder.withChild(mapBuilder.build()).build();
+ }
+
+ /**
+ * Map data by the specific module or submodule.
+ *
+ * @param mapBuilder
+ * ordered list builder for children
+ * @param moduleSch
+ * schema of list for entryMapBuilder
+ * @param isSubmodule
+ * true if module is specified as submodule, false otherwise
+ * @param module
+ * specific module or submodule
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @param context
+ * schema context
+ */
+ private static void fillMapByModules(final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder,
+ final DataSchemaNode moduleSch, final boolean isSubmodule, final Module module,
+ final Module ietfYangLibraryModule, final SchemaContext context) {
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
+ Builders.mapEntryBuilder((ListSchemaNode) moduleSch);
+ addCommonLeafs(module, mapEntryBuilder, ietfYangLibraryModule);
+ addChildOfModuleBySpecificModuleInternal(
+ IetfYangLibrary.SPECIFIC_MODULE_SCHEMA_LEAF_QNAME, mapEntryBuilder, IetfYangLibrary.BASE_URI_OF_SCHEMA
+ + module.getName() + "/" + module.getQNameModule().getFormattedRevision(),
+ ietfYangLibraryModule);
+ if (!isSubmodule) {
+ addChildOfModuleBySpecificModuleOfListChild(IetfYangLibrary.SPECIFIC_MODULE_NAMESPACE_LEAF_QNAME,
+ mapEntryBuilder, module.getNamespace().toString(), ietfYangLibraryModule);
+
+ // features - not mandatory
+ if ((module.getFeatures() != null) && !module.getFeatures().isEmpty()) {
+ addFeatureLeafList(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME, mapEntryBuilder,
+ module.getFeatures(), ietfYangLibraryModule);
+ }
+ // deviations - not mandatory
+ if ((module.getDeviations() != null) && !module.getDeviations().isEmpty()) {
+ addDeviationList(module, mapEntryBuilder, ietfYangLibraryModule, context);
+ addChildOfModuleBySpecificModuleOfListChild(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME,
+ mapEntryBuilder, ConformanceType.Implement.getName(), ietfYangLibraryModule);
+ } else {
+ addChildOfModuleBySpecificModuleOfListChild(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME,
+ mapEntryBuilder, ConformanceType.Import.getName(), ietfYangLibraryModule);
+ }
+ // submodules - not mandatory
+ if ((module.getSubmodules() != null) && !module.getSubmodules().isEmpty()) {
+ addSubmodules(module, mapEntryBuilder, ietfYangLibraryModule, context);
+ }
+ }
+ mapBuilder.withChild(mapEntryBuilder.build());
+ }
+
+ /**
+ * Mapping submodules of specific module.
+ *
+ * @param module
+ * module with submodules
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @param context
+ * schema context
+ */
+ private static void addSubmodules(final Module module,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Module ietfYangLibraryModule, final SchemaContext context) {
+ final DataSchemaNode listSubm = findSchemaInListOfModulesSchema(
+ IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME, ietfYangLibraryModule);
+ final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder =
+ Builders.orderedMapBuilder((ListSchemaNode) listSubm);
+ for (final Module submodule : module.getSubmodules()) {
+ fillMapByModules(mapBuilder, listSubm, true, submodule, ietfYangLibraryModule, context);
+ }
+ mapEntryBuilder.withChild(mapBuilder.build());
+ }
+
+ /**
+ * Mapping deviations of specific module.
+ *
+ * @param module
+ * module with deviations
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @param context
+ * schema context
+ */
+ private static void addDeviationList(final Module module,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Module ietfYangLibraryModule, final SchemaContext context) {
+ final DataSchemaNode deviationsSchema = findSchemaInListOfModulesSchema(
+ IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME, ietfYangLibraryModule);
+ final CollectionNodeBuilder<MapEntryNode, MapNode> deviations =
+ Builders.mapBuilder((ListSchemaNode) deviationsSchema);
+ for (final Deviation deviation : module.getDeviations()) {
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> deviationEntryNode =
+ Builders.mapEntryBuilder((ListSchemaNode) deviationsSchema);
+ final QName lastComponent = deviation.getTargetPath().getLastComponent();
+ addChildOfModuleBySpecificModule(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, deviationEntryNode,
+ context.findModuleByNamespaceAndRevision(lastComponent.getNamespace(), lastComponent.getRevision())
+ .getName(),
+ ietfYangLibraryModule);
+ addChildOfModuleBySpecificModule(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, deviationEntryNode,
+ lastComponent.getRevision(), ietfYangLibraryModule);
+ deviations.withChild(deviationEntryNode.build());
+ }
+ mapEntryBuilder.withChild(deviations.build());
+ }
+
+ /**
+ * Mapping features of specific module.
+ *
+ * @param qnameOfFeaturesLeafList
+ * qname of feature leaf-list in ietf-yang-library module
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param features
+ * features of specific module
+ * @param ietfYangLibraryModule
+ * ieat-yang-library module
+ */
+ private static void addFeatureLeafList(final QName qnameOfFeaturesLeafList,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Set<FeatureDefinition> features, final Module ietfYangLibraryModule) {
+ final DataSchemaNode schemaNode =
+ findSchemaInListOfModulesSchema(qnameOfFeaturesLeafList, ietfYangLibraryModule);
+ final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafSetBuilder =
+ Builders.leafSetBuilder((LeafListSchemaNode) schemaNode);
+ for (final FeatureDefinition feature : features) {
+ leafSetBuilder.withChild(Builders.leafSetEntryBuilder((LeafListSchemaNode) schemaNode)
+ .withValue(feature.getQName().getLocalName()).build());
+ }
+ mapEntryBuilder.withChild(leafSetBuilder.build());
+ }
+
+ /**
+ * Mapping common leafs (grouping common-leafs in ietf-yang-library) of
+ * specific module.
+ *
+ * @param module
+ * specific module for getting name and revision
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ */
+ private static void addCommonLeafs(final Module module,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Module ietfYangLibraryModule) {
+ addChildOfModuleBySpecificModuleInternal(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, mapEntryBuilder,
+ module.getName(), ietfYangLibraryModule);
+ addChildOfModuleBySpecificModuleInternal(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, mapEntryBuilder,
+ module.getQNameModule().getFormattedRevision(), ietfYangLibraryModule);
+ }
+
+ /**
+ * Mapping data child of grouping module-list by ietf-yang-library.
+ *
+ * @param specificQName
+ * qname of leaf in module-list grouping
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param value
+ * value of leaf
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ */
+ private static void addChildOfModuleBySpecificModuleOfListChild(final QName specificQName,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Object value, final Module ietfYangLibraryModule) {
+ final DataSchemaNode leafSch = findSchemaInListOfModulesSchema(specificQName, ietfYangLibraryModule);
+ mapEntryBuilder.withChild(Builders.leafBuilder((LeafSchemaNode) leafSch).withValue(value).build());
+ }
+
+ /**
+ * Find specific schema in gourping module-lsit.
+ *
+ * @param specificQName
+ * qname of schema
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @return schemaNode of specific child
+ */
+ private static DataSchemaNode findSchemaInListOfModulesSchema(final QName specificQName,
+ final Module ietfYangLibraryModule) {
+ for (final GroupingDefinition groupingDefinition : ietfYangLibraryModule.getGroupings()) {
+ if (groupingDefinition.getQName().equals(IetfYangLibrary.GROUPING_MODULE_LIST_QNAME)) {
+ final DataSchemaNode dataChildByName =
+ groupingDefinition.getDataChildByName(IetfYangLibrary.MODULE_QNAME_LIST);
+ return ((ListSchemaNode) dataChildByName).getDataChildByName(specificQName);
+ }
+ }
+ throw new RestconfDocumentedException(specificQName.getLocalName() + " doesn't exist.");
+ }
+
+ /**
+ * Mapping data child of internal groupings in module-list grouping.
+ *
+ * @param specifiLeafQName
+ * qnmae of leaf for mapping
+ * @param mapEntryBuilder
+ * mapEntryBuilder of parent for mapping children
+ * @param value
+ * value of leaf
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ */
+ private static void addChildOfModuleBySpecificModuleInternal(final QName specifiLeafQName,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Object value, final Module ietfYangLibraryModule) {
+ final DataSchemaNode nameLeaf = findNodeInInternGroupings(specifiLeafQName, ietfYangLibraryModule);
+ mapEntryBuilder.withChild(Builders.leafBuilder((LeafSchemaNode) nameLeaf).withValue(value).build());
+ }
+
+ /**
+ * Find schema node of leaf by qname in internal groupings of module-list.
+ * grouping
+ *
+ * @param qnameOfSchema
+ * qname of leaf
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @return schema node of specific leaf
+ */
+ private static DataSchemaNode findNodeInInternGroupings(final QName qnameOfSchema,
+ final Module ietfYangLibraryModule) {
+ for (final GroupingDefinition groupingDefinition : ietfYangLibraryModule.getGroupings()) {
+ if (groupingDefinition.getQName().equals(IetfYangLibrary.GROUPING_MODULE_LIST_QNAME)) {
+ for (final GroupingDefinition internalGrouping : groupingDefinition.getGroupings()) {
+ if (internalGrouping.getDataChildByName(qnameOfSchema) != null) {
+ return internalGrouping.getDataChildByName(qnameOfSchema);
+ }
+ }
+ }
+ }
+ throw new RestconfDocumentedException(qnameOfSchema.getLocalName() + " doesn't exist.");
+ }
+
+ /**
+ * Mapping childrens of list-module.
+ *
+ * @param specifiLeafQName
+ * qname of leaf
+ * @param mapEntryBuilder
+ * maptEntryBuilder of parent for mapping children
+ * @param value
+ * valeu of leaf
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ */
+ private static void addChildOfModuleBySpecificModule(final QName specifiLeafQName,
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
+ final Object value, final Module ietfYangLibraryModule) {
+ final DataSchemaNode nameLeaf = findNodeInGroupings(specifiLeafQName, ietfYangLibraryModule);
+ mapEntryBuilder.withChild(Builders.leafBuilder((LeafSchemaNode) nameLeaf).withValue(value).build());
+ }
+
+ /**
+ * Find schema of specific leaf in list-module grouping.
+ *
+ * @param qnameOfSchema
+ * qname of leaf
+ * @param ietfYangLibraryModule
+ * ietf-yang-library module
+ * @return schemaNode of specific leaf
+ */
+ private static DataSchemaNode findNodeInGroupings(final QName qnameOfSchema, final Module ietfYangLibraryModule) {
+ for (final GroupingDefinition groupingDefinition : ietfYangLibraryModule.getGroupings()) {
+ if (groupingDefinition.getDataChildByName(qnameOfSchema) != null) {
+ return groupingDefinition.getDataChildByName(qnameOfSchema);
+ }
+ }
+ throw new RestconfDocumentedException(qnameOfSchema.getLocalName() + " doesn't exist.");
+ }
+
+ /**
+ * Map capabilites by ietf-restconf-monitoring.
+ *
+ * @param monitoringModule
+ * ietf-restconf-monitoring module
+ * @return mapped capabilites
+ */
+ public static NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>>
+ mapCapabilites(final Module monitoringModule) {
+ final DataSchemaNode restconfState =
+ monitoringModule.getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME);
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> restStateContBuilder =
+ Builders.containerBuilder((ContainerSchemaNode) restconfState);
+ final DataSchemaNode capabilitesContSchema =
+ getChildOfCont((ContainerSchemaNode) restconfState, MonitoringModule.CONT_CAPABILITES_QNAME);
+ final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> capabilitesContBuilder =
+ Builders.containerBuilder((ContainerSchemaNode) capabilitesContSchema);
+ final DataSchemaNode leafListCapa = getChildOfCont((ContainerSchemaNode) capabilitesContSchema,
+ MonitoringModule.LEAF_LIST_CAPABILITY_QNAME);
+ final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListCapaBuilder =
+ Builders.orderedLeafSetBuilder((LeafListSchemaNode) leafListCapa);
+ fillLeafListCapa(leafListCapaBuilder, (LeafListSchemaNode) leafListCapa);
+
+ return restStateContBuilder.withChild(capabilitesContBuilder.withChild(leafListCapaBuilder.build()).build())
+ .build();
+ }
+
+ /**
+ * Map data to leaf-list.
+ *
+ * @param builder
+ * builder of parent for children
+ * @param leafListSchema
+ * leaf list schema
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static void fillLeafListCapa(final ListNodeBuilder builder, final LeafListSchemaNode leafListSchema) {
+ builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.DEPTH));
+ builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.FIELDS));
+ builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.FILTER));
+ builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.REPLAY));
+ builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.WITH_DEFAULTS));
+ }
+
+ /**
+ * Map value to leaf list entry node.
+ *
+ * @param leafListSchema
+ * leaf list schema of leaf list entry
+ * @param value
+ * value of leaf entry
+ * @return entry node
+ */
+ @SuppressWarnings("rawtypes")
+ private static LeafSetEntryNode leafListEntryBuild(final LeafListSchemaNode leafListSchema, final String value) {
+ return Builders.leafSetEntryBuilder(leafListSchema).withValue(value).build();
+ }
+
+ /**
+ * Find specific schema node by qname in parent {@link ContainerSchemaNode}.
+ *
+ * @param parent
+ * schemaNode
+ * @param childQName
+ * specific qname of child
+ * @return schema node of child by qname
+ */
+ private static DataSchemaNode getChildOfCont(final ContainerSchemaNode parent, final QName childQName) {
+ for (final DataSchemaNode child : parent.getChildNodes()) {
+ if (child.getQName().equals(childQName)) {
+ return child;
+ }
+ }
+ throw new RestconfDocumentedException(
+ childQName.getLocalName() + " doesn't exist in container " + MonitoringModule.CONT_RESTCONF_STATE_NAME);
+ }
+
+ /**
+ * Map data of yang notification to normalized node according to
+ * ietf-restconf-monitoring.
+ *
+ * @param notifiQName
+ * qname of notification from listener
+ * @param notifications
+ * list of notifications for find schema of notification by
+ * notifiQName
+ * @param start
+ * start-time query parameter of notification
+ * @param outputType
+ * output type of notification
+ * @param uri
+ * location of registered listener for sending data of
+ * notification
+ * @param monitoringModule
+ * ietf-restconf-monitoring module
+ * @param existParent
+ * true if data of parent -
+ * ietf-restconf-monitoring:restconf-state/streams - exist in DS
+ * @return mapped data of notification - map entry node if parent exists,
+ * container streams with list and map entry node if not
+ */
+ @SuppressWarnings("rawtypes")
+ public static NormalizedNode mapYangNotificationStreamByIetfRestconfMonitoring(final QName notifiQName,
+ final Set<NotificationDefinition> notifications, final Instant start, final String outputType,
+ final URI uri, final Module monitoringModule, final boolean existParent) {
+ for (final NotificationDefinition notificationDefinition : notifications) {
+ if (notificationDefinition.getQName().equals(notifiQName)) {
+ final DataSchemaNode streamListSchema = ((ContainerSchemaNode) ((ContainerSchemaNode) monitoringModule
+ .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
+ .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME))
+ .getDataChildByName(MonitoringModule.LIST_STREAM_QNAME);
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
+ Builders.mapEntryBuilder((ListSchemaNode) streamListSchema);
+
+ final ListSchemaNode listSchema = ((ListSchemaNode) streamListSchema);
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_NAME_STREAM_QNAME),
+ notificationDefinition.getQName().getLocalName());
+ if ((notificationDefinition.getDescription() != null)
+ && !notificationDefinition.getDescription().equals("")) {
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_DESCR_STREAM_QNAME),
+ notificationDefinition.getDescription());
+ }
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_REPLAY_SUPP_STREAM_QNAME), true);
+ if (start != null) {
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_START_TIME_STREAM_QNAME),
+ DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start,
+ ZoneId.systemDefault())));
+ }
+ prepareListAndFillEntryBuilder(streamEntry,
+ (ListSchemaNode) listSchema.getDataChildByName(MonitoringModule.LIST_ACCESS_STREAM_QNAME),
+ outputType, uri);
+
+ if (!existParent) {
+ final DataSchemaNode contStreamsSchema = ((ContainerSchemaNode) monitoringModule
+ .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
+ .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME);
+ return Builders.containerBuilder((ContainerSchemaNode) contStreamsSchema).withChild(Builders
+ .mapBuilder((ListSchemaNode) streamListSchema).withChild(streamEntry.build()).build())
+ .build();
+ }
+ return streamEntry.build();
+ }
+ }
+
+ throw new RestconfDocumentedException(notifiQName + " doesn't exist in any modul");
+ }
+
+ private static void prepareListAndFillEntryBuilder(
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry,
+ final ListSchemaNode listSchemaNode, final String outputType, final URI uriToWebsocketServer) {
+ final CollectionNodeBuilder<MapEntryNode, MapNode> accessListBuilder = Builders.mapBuilder(listSchemaNode);
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> entryAccessList =
+ Builders.mapEntryBuilder(listSchemaNode);
+ prepareLeafAndFillEntryBuilder(entryAccessList,
+ listSchemaNode.getDataChildByName(MonitoringModule.LEAF_ENCODING_ACCESS_QNAME), outputType);
+ prepareLeafAndFillEntryBuilder(entryAccessList,
+ listSchemaNode.getDataChildByName(MonitoringModule.LEAF_LOCATION_ACCESS_QNAME),
+ uriToWebsocketServer.toString());
+ streamEntry.withChild(accessListBuilder.withChild(entryAccessList.build()).build());
+ }
+
+ /**
+ * Prepare leaf and fill entry builder.
+ *
+ * @param streamEntry Stream entry
+ * @param leafSchema Leaf schema
+ * @param value Value
+ */
+ private static void prepareLeafAndFillEntryBuilder(
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry,
+ final DataSchemaNode leafSchema, final Object value) {
+ streamEntry.withChild(Builders.leafBuilder((LeafSchemaNode) leafSchema).withValue(value).build());
+ }
+
+ /**
+ * Map data of data change notification to normalized node according to
+ * ietf-restconf-monitoring.
+ *
+ * @param path
+ * path of data to listen on
+ * @param start
+ * start-time query parameter of notification
+ * @param outputType
+ * output type of notification
+ * @param uri
+ * location of registered listener for sending data of
+ * notification
+ * @param monitoringModule
+ * ietf-restconf-monitoring module
+ * @param existParent
+ * true if data of parent -
+ * ietf-restconf-monitoring:restconf-state/streams - exist in DS
+ * @param schemaContext
+ * schemaContext for parsing instance identifier to get schema
+ * node of data
+ * @return mapped data of notification - map entry node if parent exists,
+ * container streams with list and map entry node if not
+ */
+ @SuppressWarnings("rawtypes")
+ public static NormalizedNode mapDataChangeNotificationStreamByIetfRestconfMonitoring(
+ final YangInstanceIdentifier path, final Instant start, final String outputType, final URI uri,
+ final Module monitoringModule, final boolean existParent, final SchemaContext schemaContext) {
+ final SchemaNode schemaNode = ParserIdentifier
+ .toInstanceIdentifier(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext),
+ schemaContext, Optional.absent())
+ .getSchemaNode();
+ final DataSchemaNode streamListSchema = ((ContainerSchemaNode) ((ContainerSchemaNode) monitoringModule
+ .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
+ .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME))
+ .getDataChildByName(MonitoringModule.LIST_STREAM_QNAME);
+ final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
+ Builders.mapEntryBuilder((ListSchemaNode) streamListSchema);
+
+ final ListSchemaNode listSchema = ((ListSchemaNode) streamListSchema);
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_NAME_STREAM_QNAME),
+ schemaNode.getQName().getLocalName());
+ if ((schemaNode.getDescription() != null) && !schemaNode.getDescription().equals("")) {
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_DESCR_STREAM_QNAME),
+ schemaNode.getDescription());
+ }
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_REPLAY_SUPP_STREAM_QNAME), true);
+ prepareLeafAndFillEntryBuilder(streamEntry,
+ listSchema.getDataChildByName(MonitoringModule.LEAF_START_TIME_STREAM_QNAME),
+ DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start, ZoneId.systemDefault())));
+ prepareListAndFillEntryBuilder(streamEntry,
+ (ListSchemaNode) listSchema.getDataChildByName(MonitoringModule.LIST_ACCESS_STREAM_QNAME), outputType,
+ uri);
+
+ if (!existParent) {
+ final DataSchemaNode contStreamsSchema = ((ContainerSchemaNode) monitoringModule
+ .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
+ .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME);
+ return Builders
+ .containerBuilder((ContainerSchemaNode) contStreamsSchema).withChild(Builders
+ .mapBuilder((ListSchemaNode) streamListSchema).withChild(streamEntry.build()).build())
+ .build();
+ }
+ return streamEntry.build();
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.mapping;
+
+/**
+ * Util class for mapping entry stream.
+ *
+ */
+public final class RestconfMappingStreamConstants {
+
+ public static final String DESCRIPTION = "DESCRIPTION_PLACEHOLDER";
+ public static final Boolean REPLAY_SUPPORT = Boolean.valueOf(true);
+ public static final String REPLAY_LOG = "";
+ public static final String EVENTS = "";
+
+ private RestconfMappingStreamConstants() {
+ throw new UnsupportedOperationException("Util class");
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Codec for identifier to serialize {@link YangInstanceIdentifier} to
+ * {@link String} and deserialize {@link String} to
+ * {@link YangInstanceIdentifier}.
+ *
+ */
+public final class IdentifierCodec {
+
+ private IdentifierCodec() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ public static String serialize(final YangInstanceIdentifier data, final SchemaContext schemaContext) {
+ return YangInstanceIdentifierSerializer.create(schemaContext, data);
+ }
+
+ public static YangInstanceIdentifier deserialize(final String data, final SchemaContext schemaContext) {
+ if (data == null) {
+ return YangInstanceIdentifier.builder().build();
+ }
+ return YangInstanceIdentifier.create(YangInstanceIdentifierDeserializer.create(schemaContext, data));
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class ParserFieldsParameter {
+ private static final char COLON = ':';
+ private static final char SEMICOLON = ';';
+ private static final char SLASH = '/';
+ private static final char STARTING_PARENTHESIS = '(';
+ private static final char CLOSING_PARENTHESIS = ')';
+
+ /**
+ * Parse fields parameter and return complete list of child nodes organized into levels.
+ * @param identifier identifier context created from request URI
+ * @param input input value of fields parameter
+ * @return {@link List}
+ */
+ @Nonnull
+ public static List<Set<QName>> parseFieldsParameter(@Nonnull final InstanceIdentifierContext<?> identifier,
+ @Nonnull final String input) {
+ final List<Set<QName>> parsed = new ArrayList<>();
+ final SchemaContext context = identifier.getSchemaContext();
+ final QNameModule startQNameModule = identifier.getSchemaNode().getQName().getModule();
+ final DataSchemaContextNode<?> startNode = DataSchemaContextNode.fromDataSchemaNode(
+ (DataSchemaNode) identifier.getSchemaNode());
+
+ if (startNode == null) {
+ throw new RestconfDocumentedException(
+ "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ parseInput(input, startQNameModule, startNode, parsed, context);
+ return parsed;
+ }
+
+ /**
+ * Parse input value of fields parameter and create list of sets. Each set represents one level of child nodes.
+ * @param input input value of fields parameter
+ * @param startQNameModule starting qname module
+ * @param startNode starting node
+ * @param parsed list of results
+ * @param context schema context
+ */
+ private static void parseInput(@Nonnull final String input,
+ @Nonnull final QNameModule startQNameModule,
+ @Nonnull final DataSchemaContextNode<?> startNode,
+ @Nonnull final List<Set<QName>> parsed,
+ @Nonnull final SchemaContext context) {
+ int currentPosition = 0;
+ int startPosition = 0;
+ DataSchemaContextNode<?> currentNode = startNode;
+ QNameModule currentQNameModule = startQNameModule;
+
+ Set<QName> currentLevel = new HashSet<>();
+ parsed.add(currentLevel);
+
+ while (currentPosition < input.length()) {
+ final char currentChar = input.charAt(currentPosition);
+
+ if (Deserializer.IDENTIFIER.matches(currentChar) || currentChar == '/') {
+ if (currentChar == SLASH) {
+ // add parsed identifier to results for current level
+ currentNode = addChildToResult(
+ currentNode,
+ input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
+ // go one level down
+ currentLevel = new HashSet<>();
+ parsed.add(currentLevel);
+
+ currentPosition++;
+ startPosition = currentPosition;
+ } else {
+ currentPosition++;
+ }
+
+ continue;
+ }
+
+ switch (currentChar) {
+ case COLON :
+ // new namespace and revision found
+ currentQNameModule = context.findModuleByName(
+ input.substring(startPosition, currentPosition), null).getQNameModule();
+ currentPosition++;
+ break;
+ case STARTING_PARENTHESIS:
+ // add current child to parsed results for current level
+ final DataSchemaContextNode<?> child = addChildToResult(
+ currentNode,
+ input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
+ // call with child node as new start node for one level down
+ final int closingParenthesis = currentPosition
+ + findClosingParenthesis(input.substring(currentPosition + 1));
+ parseInput(
+ input.substring(currentPosition + 1, closingParenthesis),
+ currentQNameModule,
+ child,
+ parsed,
+ context);
+
+ // closing parenthesis must be at the end of input or separator and one more character is expected
+ currentPosition = closingParenthesis + 1;
+ if (currentPosition != input.length()) {
+ if (currentPosition + 1 < input.length()) {
+ if (input.charAt(currentPosition) == SEMICOLON) {
+ currentPosition++;
+ } else {
+ throw new RestconfDocumentedException(
+ "Missing semicolon character after "
+ + child.getIdentifier().getNodeType().getLocalName()
+ + " child nodes",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ } else {
+ throw new RestconfDocumentedException(
+ "Unexpected character '"
+ + input.charAt(currentPosition)
+ + "' found in fields parameter value",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ }
+
+ break;
+ case SEMICOLON:
+ // complete identifier found
+ addChildToResult(
+ currentNode,
+ input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
+ currentPosition++;
+ break;
+ default:
+ throw new RestconfDocumentedException(
+ "Unexpected character '" + currentChar + "' found in fields parameter value",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ startPosition = currentPosition;
+ }
+
+ // parse input to end
+ if (startPosition < input.length()) {
+ addChildToResult(currentNode, input.substring(startPosition), currentQNameModule, currentLevel);
+ }
+ }
+
+ /**
+ * Add parsed child of current node to result for current level.
+ * @param currentNode current node
+ * @param identifier parsed identifier of child node
+ * @param currentQNameModule current namespace and revision in {@link QNameModule}
+ * @param level current nodes level
+ * @return {@link DataSchemaContextNode}
+ */
+ @Nonnull
+ private static DataSchemaContextNode<?> addChildToResult(
+ @Nonnull final DataSchemaContextNode<?> currentNode,
+ @Nonnull final String identifier,
+ @Nonnull final QNameModule currentQNameModule,
+ @Nonnull final Set<QName> level) {
+ final QName childQName = QName.create(
+ currentQNameModule.getNamespace().toString(),
+ identifier,
+ currentQNameModule.getRevision());
+
+ // resolve parent node
+ final DataSchemaContextNode<?> parentNode = resolveMixinNode(
+ currentNode, level, currentNode.getIdentifier().getNodeType());
+ if (parentNode == null) {
+ throw new RestconfDocumentedException(
+ "Not-mixin node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ // resolve child node
+ final DataSchemaContextNode<?> childNode = resolveMixinNode(
+ parentNode.getChild(childQName), level, childQName);
+ if (childNode == null) {
+ throw new RestconfDocumentedException(
+ "Child " + identifier + " node missing in "
+ + currentNode.getIdentifier().getNodeType().getLocalName(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ // add final childNode node to level nodes
+ level.add(childNode.getIdentifier().getNodeType());
+ return childNode;
+ }
+
+ /**
+ * Resolve mixin node by searching for inner nodes until not mixin node or null is found.
+ * All nodes expect of not mixin node are added to current level nodes.
+ * @param node initial mixin or not-mixin node
+ * @param level current nodes level
+ * @param qualifiedName qname of initial node
+ * @return {@link DataSchemaContextNode}
+ */
+ @Nullable
+ private static DataSchemaContextNode<?> resolveMixinNode(@Nullable final DataSchemaContextNode<?> node,
+ @Nonnull final Set<QName> level,
+ @Nonnull final QName qualifiedName) {
+ DataSchemaContextNode<?> currentNode = node;
+ while (currentNode != null && currentNode.isMixin()) {
+ level.add(qualifiedName);
+ currentNode = currentNode.getChild(qualifiedName);
+ }
+
+ return currentNode;
+ }
+
+ /**
+ * Find position of matching parenthesis increased by one, but at most equals to input size.
+ * @param input input where to find for closing parenthesis
+ * @return int position of closing parenthesis increased by one
+ */
+ private static int findClosingParenthesis(@Nonnull final String input) {
+ int position = 0;
+ int count = 1;
+
+ while (position < input.length()) {
+ final char currentChar = input.charAt(position);
+
+ if (currentChar == STARTING_PARENTHESIS) {
+ count++;
+ }
+
+ if (currentChar == CLOSING_PARENTHESIS) {
+ count--;
+ }
+
+ if (count == 0) {
+ break;
+ }
+
+ position++;
+ }
+
+ // closing parenthesis was not found
+ if (position >= input.length()) {
+ throw new RestconfDocumentedException("Missing closing parenthesis in fields parameter",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+
+ return ++position;
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.schema.SchemaExportContext;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.validations.RestconfValidation;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util class for parsing identifier.
+ */
+public final class ParserIdentifier {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ParserIdentifier.class);
+
+ private ParserIdentifier() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Make {@link InstanceIdentifierContext} from {@link String} identifier
+ * <br>
+ * For identifiers of data NOT behind mount points returned
+ * {@link InstanceIdentifierContext} is prepared with {@code null} reference of {@link DOMMountPoint} and with
+ * controller's {@link SchemaContext}.
+ * <br>
+ * For identifiers of data behind mount points returned
+ * {@link InstanceIdentifierContext} is prepared with reference of {@link DOMMountPoint} and its
+ * own {@link SchemaContext}.
+ *
+ * @param identifier
+ * - path identifier
+ * @param schemaContext
+ * - controller schema context
+ * @param mountPointService
+ * - mount point service
+ * @return {@link InstanceIdentifierContext}
+ */
+ public static InstanceIdentifierContext<?> toInstanceIdentifier(
+ final String identifier,
+ final SchemaContext schemaContext,
+ final Optional<DOMMountPointService> mountPointService) {
+ if (identifier != null && identifier.contains(RestconfConstants.MOUNT)) {
+ if (!mountPointService.isPresent()) {
+ throw new RestconfDocumentedException("Mount point service is not available");
+ }
+
+ final Iterator<String> pathsIt = Splitter.on("/" + RestconfConstants.MOUNT).split(identifier).iterator();
+
+ final String mountPointId = pathsIt.next();
+ final YangInstanceIdentifier mountYangInstanceIdentifier = IdentifierCodec.deserialize(
+ mountPointId, schemaContext);
+ final Optional<DOMMountPoint> mountPoint =
+ mountPointService.get().getMountPoint(mountYangInstanceIdentifier);
+
+ if (!mountPoint.isPresent()) {
+ throw new RestconfDocumentedException(
+ "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+ }
+
+ final DOMMountPoint domMountPoint = mountPoint.get();
+ final SchemaContext mountSchemaContext = domMountPoint.getSchemaContext();
+
+ final String pathId = pathsIt.next().replaceFirst("/", "");
+ final YangInstanceIdentifier pathYangInstanceIdentifier = IdentifierCodec.deserialize(
+ pathId, mountSchemaContext);
+
+ final DataSchemaContextNode<?> child = DataSchemaContextTree.from(mountSchemaContext)
+ .getChild(pathYangInstanceIdentifier);
+ if (child != null) {
+ return new InstanceIdentifierContext<SchemaNode>(pathYangInstanceIdentifier, child.getDataSchemaNode(),
+ domMountPoint, mountSchemaContext);
+ }
+ final QName rpcQName = pathYangInstanceIdentifier.getLastPathArgument().getNodeType();
+ RpcDefinition def = null;
+ for (final RpcDefinition rpcDefinition : mountSchemaContext
+ .findModuleByNamespaceAndRevision(rpcQName.getNamespace(), rpcQName.getRevision()).getRpcs()) {
+ if (rpcDefinition.getQName().getLocalName().equals(rpcQName.getLocalName())) {
+ def = rpcDefinition;
+ break;
+ }
+ }
+ return new InstanceIdentifierContext<>(pathYangInstanceIdentifier, def, domMountPoint, mountSchemaContext);
+ } else {
+ final YangInstanceIdentifier deserialize = IdentifierCodec.deserialize(identifier, schemaContext);
+ final DataSchemaContextNode<?> child = DataSchemaContextTree.from(schemaContext).getChild(deserialize);
+
+ if (child != null) {
+ return new InstanceIdentifierContext<SchemaNode>(
+ deserialize, child.getDataSchemaNode(), null, schemaContext);
+ }
+ final QName rpcQName = deserialize.getLastPathArgument().getNodeType();
+ RpcDefinition def = null;
+ for (final RpcDefinition rpcDefinition
+ : schemaContext.findModuleByNamespaceAndRevision(rpcQName.getNamespace(),
+ rpcQName.getRevision()).getRpcs()) {
+ if (rpcDefinition.getQName().getLocalName().equals(rpcQName.getLocalName())) {
+ def = rpcDefinition;
+ break;
+ }
+ }
+ return new InstanceIdentifierContext<>(deserialize, def, null, schemaContext);
+ }
+ }
+
+ /**
+ * Make {@link String} from {@link YangInstanceIdentifier}.
+ *
+ * @param instanceIdentifier Instance identifier
+ * @param schemaContext Schema context
+ * @return Yang instance identifier serialized to String
+ */
+ public static String stringFromYangInstanceIdentifier(final YangInstanceIdentifier instanceIdentifier,
+ final SchemaContext schemaContext) {
+ return IdentifierCodec.serialize(instanceIdentifier, schemaContext);
+ }
+
+ /**
+ * Make a {@link QName} from identifier.
+ *
+ * @param identifier
+ * path parameter
+ * @return {@link QName}
+ */
+ public static QName makeQNameFromIdentifier(final String identifier) {
+ // check if more than one slash is not used as path separator
+ if (identifier.contains(
+ String.valueOf(RestconfConstants.SLASH).concat(String.valueOf(RestconfConstants.SLASH)))) {
+ 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);
+ }
+
+ final int mountIndex = identifier.indexOf(RestconfConstants.MOUNT);
+ final String moduleNameAndRevision;
+ if (mountIndex >= 0) {
+ moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length())
+ .replaceFirst(String.valueOf(RestconfConstants.SLASH), "");
+ } else {
+ moduleNameAndRevision = identifier;
+ }
+
+ final List<String> pathArgs = RestconfConstants.SLASH_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);
+ }
+
+ final Date moduleRevision;
+ try {
+ moduleRevision = SimpleDateFormatUtil.getRevisionFormat().parse(pathArgs.get(1));
+ } catch (final ParseException 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);
+ }
+
+ return QName.create(null, moduleRevision, pathArgs.get(0));
+ }
+
+ /**
+ * Parsing {@link Module} module by {@link String} module name and
+ * {@link Date} revision and from the parsed module create
+ * {@link SchemaExportContext}.
+ *
+ * @param schemaContext
+ * {@link SchemaContext}
+ * @param identifier
+ * path parameter
+ * @param domMountPointService
+ * {@link DOMMountPointService}
+ * @return {@link SchemaExportContext}
+ */
+ public static SchemaExportContext toSchemaExportContextFromIdentifier(final SchemaContext schemaContext,
+ final String identifier, final DOMMountPointService domMountPointService) {
+ final Iterable<String> pathComponents = RestconfConstants.SLASH_SPLITTER.split(identifier);
+ final Iterator<String> componentIter = pathComponents.iterator();
+ if (!Iterables.contains(pathComponents, RestconfConstants.MOUNT)) {
+ final String moduleName = RestconfValidation.validateAndGetModulName(componentIter);
+ final Date revision = RestconfValidation.validateAndGetRevision(componentIter);
+ final Module module = schemaContext.findModuleByName(moduleName, revision);
+ return new SchemaExportContext(schemaContext, module);
+ } else {
+ final StringBuilder pathBuilder = new StringBuilder();
+ while (componentIter.hasNext()) {
+ final String current = componentIter.next();
+
+ if (RestconfConstants.MOUNT.equals(current)) {
+ pathBuilder.append("/");
+ pathBuilder.append(RestconfConstants.MOUNT);
+ break;
+ }
+
+ if (pathBuilder.length() != 0) {
+ pathBuilder.append("/");
+ }
+
+ pathBuilder.append(current);
+ }
+ final InstanceIdentifierContext<?> point = ParserIdentifier
+ .toInstanceIdentifier(pathBuilder.toString(), schemaContext, Optional.of(domMountPointService));
+ final String moduleName = RestconfValidation.validateAndGetModulName(componentIter);
+ final Date revision = RestconfValidation.validateAndGetRevision(componentIter);
+ final Module module = point.getMountPoint().getSchemaContext().findModuleByName(moduleName, revision);
+ return new SchemaExportContext(point.getMountPoint().getSchemaContext(), module);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.common.util.RestUtil;
+import org.opendaylight.restconf.common.util.RestconfSchemaUtil;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
+import org.opendaylight.restconf.nb.rfc8040.codecs.RestCodec;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.yangtools.concepts.Codec;
+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.PathArgument;
+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.IdentitySchemaNode;
+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.SchemaContext;
+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.SchemaContextUtil;
+
+/**
+ * Deserializer for {@link String} to {@link YangInstanceIdentifier} for
+ * restconf.
+ *
+ */
+public final class YangInstanceIdentifierDeserializer {
+
+ private YangInstanceIdentifierDeserializer() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Method to create {@link Iterable} from {@link PathArgument} which are
+ * parsing from data by {@link SchemaContext}.
+ *
+ * @param schemaContext
+ * for validate of parsing path arguments
+ * @param data
+ * path to data
+ * @return {@link Iterable} of {@link PathArgument}
+ */
+ public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
+ final List<PathArgument> path = new LinkedList<>();
+ final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(
+ data, DataSchemaContextTree.from(schemaContext).getRoot(),
+ YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
+
+ while (!allCharsConsumed(variables)) {
+ validArg(variables);
+ final QName qname = prepareQName(variables);
+
+ // this is the last identifier (input is consumed) or end of identifier (slash)
+ if (allCharsConsumed(variables)
+ || (currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH)) {
+ prepareIdentifier(qname, path, variables);
+ if (variables.getCurrent() == null) {
+ path.add(NodeIdentifier.create(qname));
+ } else {
+ path.add(variables.getCurrent().getIdentifier());
+ }
+ } else if (currentChar(variables.getOffset(),
+ variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
+ if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
+ prepareNodeWithPredicates(qname, path, variables);
+ } else {
+ prepareNodeWithValue(qname, path, variables);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position "
+ + variables.getOffset() + ".");
+ }
+ }
+
+ return ImmutableList.copyOf(path);
+ }
+
+ private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
+ final MainVarsWrapper variables) {
+
+ final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
+ checkValid((dataSchemaNode != null), "Data schema node is null", variables.getData(), variables.getOffset());
+
+ final Iterator<QName> keys = ((ListSchemaNode) dataSchemaNode).getKeyDefinition().iterator();
+ final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
+
+ // skip already expected equal sign
+ skipCurrentChar(variables);
+
+ // read key value separated by comma
+ while (keys.hasNext() && !allCharsConsumed(variables) && (currentChar(variables.getOffset(),
+ variables.getData()) != RestconfConstants.SLASH)) {
+
+ // empty key value
+ if (currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) {
+ values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
+ skipCurrentChar(variables);
+ continue;
+ }
+
+ // check if next value is parsable
+ RestconfValidationUtils.checkDocumentedError(
+ ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE
+ .matches(currentChar(variables.getOffset(), variables.getData())),
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.MALFORMED_MESSAGE,
+ ""
+ );
+
+ // parse value
+ final QName key = keys.next();
+ DataSchemaNode leafSchemaNode = null;
+ if (dataSchemaNode instanceof ListSchemaNode) {
+ leafSchemaNode = ((ListSchemaNode) dataSchemaNode).getDataChildByName(key);
+ } else if (dataSchemaNode instanceof LeafListSchemaNode) {
+ leafSchemaNode = dataSchemaNode;
+ }
+ final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(
+ ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables));
+ final Object valueByType = prepareValueByType(leafSchemaNode, value, variables);
+ values.put(key, valueByType);
+
+
+ // skip comma
+ if (keys.hasNext() && !allCharsConsumed(variables) && (currentChar(
+ variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA)) {
+ skipCurrentChar(variables);
+ }
+ }
+
+ // the last key is considered to be empty
+ if (keys.hasNext()) {
+ if (allCharsConsumed(variables)
+ || (currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH)) {
+ values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
+ }
+
+ // there should be no more missing keys
+ RestconfValidationUtils.checkDocumentedError(
+ !keys.hasNext(),
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.MISSING_ATTRIBUTE,
+ "Key value missing for: " + qname
+ );
+ }
+
+ path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build()));
+ }
+
+ private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value,
+ final MainVarsWrapper vars) {
+ Object decoded = null;
+
+ TypeDefinition<? extends TypeDefinition<?>> typedef = null;
+ if (schemaNode instanceof LeafListSchemaNode) {
+ typedef = ((LeafListSchemaNode) schemaNode).getType();
+ } else {
+ typedef = ((LeafSchemaNode) schemaNode).getType();
+ }
+ final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
+ if (baseType instanceof LeafrefTypeDefinition) {
+ typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, vars.getSchemaContext(),
+ schemaNode);
+ }
+ final Codec<Object, Object> codec = RestCodec.from(typedef, null, vars.getSchemaContext());
+ decoded = codec.deserialize(value);
+ if (decoded == null) {
+ if ((baseType instanceof IdentityrefTypeDefinition)) {
+ decoded = toQName(value, schemaNode, vars.getSchemaContext());
+ }
+ }
+ return decoded;
+ }
+
+ private static Object toQName(final String value, final DataSchemaNode schemaNode,
+ final SchemaContext schemaContext) {
+ final String moduleName = toModuleName(value);
+ final String nodeName = toNodeName(value);
+ final Module module = schemaContext.findModuleByName(moduleName, null);
+ for (final IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
+ final QName qName = identitySchemaNode.getQName();
+ if (qName.getLocalName().equals(nodeName)) {
+ return qName;
+ }
+ }
+ return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName);
+ }
+
+ private static String toNodeName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return str;
+ }
+
+ if (str.indexOf(':', idx + 1) != -1) {
+ return str;
+ }
+
+ return str.substring(idx + 1);
+ }
+
+ private static String toModuleName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return null;
+ }
+
+ if (str.indexOf(':', idx + 1) != -1) {
+ return null;
+ }
+
+ return str.substring(0, idx);
+ }
+
+ private static QName prepareQName(final MainVarsWrapper variables) {
+ checkValid(
+ ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
+ .matches(currentChar(variables.getOffset(), variables.getData())),
+ "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), variables.getOffset());
+ final String preparedPrefix = nextIdentifierFromNextSequence(
+ ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
+ final String prefix;
+ final String localName;
+
+ if (allCharsConsumed(variables)) {
+ return getQNameOfDataSchemaNode(preparedPrefix, variables);
+ }
+
+ switch (currentChar(variables.getOffset(), variables.getData())) {
+ case RestconfConstants.SLASH:
+ prefix = preparedPrefix;
+ return getQNameOfDataSchemaNode(prefix, variables);
+ case ParserBuilderConstants.Deserializer.COLON:
+ prefix = preparedPrefix;
+ skipCurrentChar(variables);
+ checkValid(
+ ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
+ .matches(currentChar(variables.getOffset(), variables.getData())),
+ "Identifier must start with character from set 'a-zA-Z_'", variables.getData(),
+ variables.getOffset());
+ localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
+
+ if (!allCharsConsumed(variables) && (currentChar(
+ variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL)) {
+ return getQNameOfDataSchemaNode(localName, variables);
+ } else {
+ final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
+ Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix);
+ return QName.create(module.getQNameModule(), localName);
+ }
+ case ParserBuilderConstants.Deserializer.EQUAL:
+ prefix = preparedPrefix;
+ return getQNameOfDataSchemaNode(prefix, variables);
+ default:
+ throw new IllegalArgumentException("Failed build path.");
+ }
+ }
+
+ private static String nextIdentifierFromNextSequence(final CharMatcher matcher, final MainVarsWrapper variables) {
+ final int start = variables.getOffset();
+ nextSequenceEnd(matcher, variables);
+ return variables.getData().substring(start, variables.getOffset());
+ }
+
+ private static void nextSequenceEnd(final CharMatcher matcher, final MainVarsWrapper variables) {
+ while (!allCharsConsumed(variables)
+ && matcher.matches(variables.getData().charAt(variables.getOffset()))) {
+ variables.setOffset(variables.getOffset() + 1);
+ }
+ }
+
+ private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
+ final MainVarsWrapper variables) {
+ skipCurrentChar(variables);
+ final String value = nextIdentifierFromNextSequence(
+ ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables);
+
+ // exception if value attribute is missing
+ RestconfValidationUtils.checkDocumentedError(
+ !value.isEmpty(),
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.MISSING_ATTRIBUTE,
+ "Value missing for: " + qname
+ );
+ final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
+ final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value), variables);
+ path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, valueByType));
+ }
+
+ private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
+ final MainVarsWrapper variables) {
+ final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path, variables);
+ if (currentNode == null) {
+ return;
+ }
+ checkValid(!currentNode.isKeyedEntry(), "Entry " + qname + " requires key or value predicate to be present",
+ variables.getData(), variables.getOffset());
+ }
+
+ private static DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path,
+ final MainVarsWrapper variables) {
+ variables.setCurrent(variables.getCurrent().getChild(qname));
+ DataSchemaContextNode<?> current = variables.getCurrent();
+ if (current == null) {
+ for (final RpcDefinition rpcDefinition : variables.getSchemaContext()
+ .findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision()).getRpcs()) {
+ if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) {
+ return null;
+ }
+ }
+ }
+ checkValid(current != null, qname + " is not correct schema node identifier.", variables.getData(),
+ variables.getOffset());
+ while (current.isMixin()) {
+ path.add(current.getIdentifier());
+ current = current.getChild(qname);
+ variables.setCurrent(current);
+ }
+ return current;
+ }
+
+ private static String findAndParsePercentEncoded(final String preparedPrefix) {
+ if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
+ return preparedPrefix;
+ }
+
+ final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
+ final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
+
+ while (matcher.matchesAnyOf(parsedPrefix)) {
+ final int percentCharPosition = matcher.indexIn(parsedPrefix);
+ parsedPrefix.replace(
+ percentCharPosition,
+ percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR,
+ String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
+ percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR,
+ percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR),
+ ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX)));
+ }
+
+ return parsedPrefix.toString();
+ }
+
+ private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
+ final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
+ if (dataSchemaNode instanceof ContainerSchemaNode) {
+ final ContainerSchemaNode contSchemaNode = (ContainerSchemaNode) dataSchemaNode;
+ final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(contSchemaNode.getChildNodes(),
+ nodeName);
+ return node.getQName();
+ } else if (dataSchemaNode instanceof ListSchemaNode) {
+ final ListSchemaNode listSchemaNode = (ListSchemaNode) dataSchemaNode;
+ final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(listSchemaNode.getChildNodes(),
+ nodeName);
+ return node.getQName();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
+ return schemaContext.findModuleByName(prefix, null);
+ }
+
+ private static void validArg(final MainVarsWrapper variables) {
+ // every identifier except of the first MUST start with slash
+ if (variables.getOffset() != MainVarsWrapper.STARTING_OFFSET) {
+ checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()),
+ "Identifier must start with '/'.", variables.getData(), variables.getOffset());
+
+ // skip slash
+ skipCurrentChar(variables);
+
+ // check if slash is not also the last char in identifier
+ checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.",
+ variables.getData(), variables.getOffset());
+ }
+ }
+
+ private static void skipCurrentChar(final MainVarsWrapper variables) {
+ variables.setOffset(variables.getOffset() + 1);
+ }
+
+ private static char currentChar(final int offset, final String data) {
+ return data.charAt(offset);
+ }
+
+ private static void checkValid(final boolean condition, final String errorMsg, final String data,
+ final int offset) {
+ Preconditions.checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s",
+ data, offset, errorMsg);
+ }
+
+ private static boolean allCharsConsumed(final MainVarsWrapper variables) {
+ return variables.getOffset() == variables.getData().length();
+ }
+
+ private static final class MainVarsWrapper {
+ private static final int STARTING_OFFSET = 0;
+
+ private final SchemaContext schemaContext;
+ private final String data;
+
+ private DataSchemaContextNode<?> current;
+ private int offset;
+
+ MainVarsWrapper(final String data, final DataSchemaContextNode<?> current, final int offset,
+ final SchemaContext schemaContext) {
+ this.data = data;
+ this.current = current;
+ this.offset = offset;
+ this.schemaContext = schemaContext;
+ }
+
+ public String getData() {
+ return this.data;
+ }
+
+ public DataSchemaContextNode<?> getCurrent() {
+ return this.current;
+ }
+
+ public void setCurrent(final DataSchemaContextNode<?> current) {
+ this.current = current;
+ }
+
+ public int getOffset() {
+ return this.offset;
+ }
+
+ public void setOffset(final int offset) {
+ this.offset = offset;
+ }
+
+ public SchemaContext getSchemaContext() {
+ return this.schemaContext;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser;
+
+import com.google.common.base.Preconditions;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Serializer;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+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.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
+ */
+public final class YangInstanceIdentifierSerializer {
+
+ private YangInstanceIdentifierSerializer() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Method to create String from {@link Iterable} of {@link PathArgument}
+ * which are parsing from data by {@link SchemaContext}.
+ *
+ * @param schemaContext
+ * for validate of parsing path arguments
+ * @param data
+ * path to data
+ * @return {@link String}
+ */
+ public static String create(final SchemaContext schemaContext, final YangInstanceIdentifier data) {
+ final DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
+ final MainVarsWrapper variables = new MainVarsWrapper(current);
+ final StringBuilder path = new StringBuilder();
+
+ QNameModule parentModule = null;
+ for (int i = 0; i < data.getPathArguments().size(); i++) {
+ // get module of the parent
+ if (!variables.getCurrent().isMixin()) {
+ parentModule = variables.getCurrent().getDataSchemaNode().getQName().getModule();
+ }
+
+ final PathArgument arg = data.getPathArguments().get(i);
+ variables.setCurrent(variables.getCurrent().getChild(arg));
+
+ Preconditions.checkArgument(variables.getCurrent() != null,
+ "Invalid input %s: schema for argument %s (after %s) not found", data, arg, path);
+
+ if (variables.getCurrent().isMixin()) {
+ continue;
+ }
+
+ // append namespace before every node which is defined in other module than its parent
+ // condition is satisfied also for the first path argument
+ if (!arg.getNodeType().getModule().equals(parentModule)) {
+ // append slash if it is not the first path argument
+ if (path.length() > 0) {
+ path.append(RestconfConstants.SLASH);
+ }
+
+ path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
+ path.append(ParserBuilderConstants.Deserializer.COLON);
+ } else {
+ path.append(RestconfConstants.SLASH);
+ }
+
+ if (arg instanceof NodeIdentifierWithPredicates) {
+ prepareNodeWithPredicates(path, arg);
+ } else if (arg instanceof NodeWithValue) {
+ prepareNodeWithValue(path, arg);
+ } else {
+ appendQName(path, arg.getNodeType());
+ }
+ }
+
+ return path.toString();
+ }
+
+ private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
+ path.append(arg.getNodeType().getLocalName());
+ path.append(ParserBuilderConstants.Deserializer.EQUAL);
+
+ String value = String.valueOf(((NodeWithValue<String>) arg).getValue());
+ if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
+ value = parsePercentEncodeChars(value);
+ }
+ path.append(value);
+ }
+
+ private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
+ path.append(arg.getNodeType().getLocalName());
+
+ final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).getKeyValues()
+ .entrySet().iterator();
+
+ if (iterator.hasNext()) {
+ path.append(ParserBuilderConstants.Deserializer.EQUAL);
+ }
+
+ while (iterator.hasNext()) {
+ String valueOf = String.valueOf(iterator.next().getValue());
+ if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
+ valueOf = parsePercentEncodeChars(valueOf);
+ }
+ path.append(valueOf);
+ if (iterator.hasNext()) {
+ path.append(ParserBuilderConstants.Deserializer.COMMA);
+ }
+ }
+ }
+
+ /**
+ * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars.
+ *
+ * @param valueOf
+ * string to encode
+ * @return encoded {@link String}
+ */
+ private static String parsePercentEncodeChars(final String valueOf) {
+ final StringBuilder sb = new StringBuilder();
+ int start = 0;
+ while (start < valueOf.length()) {
+ if (Serializer.PERCENT_ENCODE_CHARS.matches(valueOf.charAt(start))) {
+ final String format = String.format("%x", (int) valueOf.charAt(start));
+ final String upperCase = format.toUpperCase();
+ sb.append(ParserBuilderConstants.Deserializer.PERCENT_ENCODING + upperCase);
+ } else {
+ sb.append(valueOf.charAt(start));
+ }
+ start++;
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Add {@link QName} to the serialized string.
+ *
+ * @param path
+ * {@link StringBuilder}
+ * @param qname
+ * {@link QName} node
+ * @return {@link StringBuilder}
+ */
+ private static StringBuilder appendQName(final StringBuilder path, final QName qname) {
+ path.append(qname.getLocalName());
+ return path;
+ }
+
+ /**
+ * Create prefix of namespace from {@link QName}.
+ *
+ * @param qname
+ * {@link QName}
+ * @return {@link String}
+ */
+ private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
+ final URI namespace = qname.getNamespace();
+ Preconditions.checkArgument(namespace != null, "Failed to map QName {}", qname);
+ final Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, qname.getRevision());
+ return module.getName();
+ }
+
+ private static final class MainVarsWrapper {
+
+ private DataSchemaContextNode<?> current;
+
+ MainVarsWrapper(final DataSchemaContextNode<?> current) {
+ this.setCurrent(current);
+ }
+
+ public DataSchemaContextNode<?> getCurrent() {
+ return this.current;
+ }
+
+ public void setCurrent(final DataSchemaContextNode<?> current) {
+ this.current = current;
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.parser.builder;
+
+import com.google.common.base.CharMatcher;
+import java.util.Arrays;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierDeserializer;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierSerializer;
+
+/**
+ * Util class of constants of {@link YangInstanceIdentifierSerializer} and
+ * {@link YangInstanceIdentifierDeserializer}.
+ *
+ */
+public final class ParserBuilderConstants {
+
+ private ParserBuilderConstants() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ /**
+ * Constants for {@link YangInstanceIdentifierSerializer}.
+ *
+ */
+ public static final class Serializer {
+
+ private Serializer() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ public static final String DISABLED_CHARS = Arrays.toString(new char[] { ':', '/', '?', '#', '[', ']', '@' })
+ .concat(Arrays.toString(new char[] { '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' }));
+
+ public static final CharMatcher PERCENT_ENCODE_CHARS = CharMatcher.anyOf(DISABLED_CHARS).precomputed();
+ }
+
+ /**
+ * Constants for {@link YangInstanceIdentifierSerializer}.
+ *
+ */
+ public static final class Deserializer {
+
+ private Deserializer() {
+ throw new UnsupportedOperationException("Util class");
+ }
+
+ public static final CharMatcher BASE = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z'))
+ .precomputed();
+
+ public static final CharMatcher IDENTIFIER_FIRST_CHAR = BASE.or(CharMatcher.is('_')).precomputed();
+
+ public static final CharMatcher IDENTIFIER = IDENTIFIER_FIRST_CHAR.or(CharMatcher.inRange('0', '9'))
+ .or(CharMatcher.anyOf(".-")).precomputed();
+
+ public static final CharMatcher IDENTIFIER_HEXA = CharMatcher.inRange('a', 'f')
+ .or(CharMatcher.inRange('A', 'F')).or(CharMatcher.inRange('0', '9')).precomputed();
+
+ public static final char COLON = ':';
+ public static final char EQUAL = '=';
+ public static final char COMMA = ',';
+ public static final char HYPHEN = '-';
+ public static final char PERCENT_ENCODING = '%';
+
+ public static final CharMatcher IDENTIFIER_PREDICATE = CharMatcher.noneOf(
+ Serializer.DISABLED_CHARS).precomputed();
+
+ public static final String EMPTY_STRING = "";
+
+ // position of the first encoded char after percent sign in percent encoded string
+ public static final int FIRST_ENCODED_CHAR = 1;
+ // position of the last encoded char after percent sign in percent encoded string
+ public static final int LAST_ENCODED_CHAR = 3;
+ // percent encoded radix for parsing integers
+ public static final int PERCENT_ENCODED_RADIX = 16;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.utils.validations;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
+import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+
+/**
+ * Util class for validations.
+ *
+ */
+public final class RestconfValidation {
+
+ private RestconfValidation() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Validation and parsing of revision.
+ *
+ * @param revisionDate
+ * iterator
+ * @return {@link Date}
+ */
+ public static Date validateAndGetRevision(final Iterator<String> revisionDate) {
+ RestconfValidationUtils.checkDocumentedError(revisionDate.hasNext(), ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE, "Revision date must be supplied.");
+ try {
+ return SimpleDateFormatUtil.getRevisionFormat().parse(revisionDate.next());
+ } catch (final ParseException e) {
+ throw new RestconfDocumentedException("Supplied revision is not in expected date format YYYY-mm-dd", e);
+ }
+ }
+
+ /**
+ * Validation of name.
+ *
+ * @param moduleName
+ * iterator
+ * @return {@link String}
+ */
+ public static String validateAndGetModulName(final Iterator<String> moduleName) {
+ RestconfValidationUtils.checkDocumentedError(
+ moduleName.hasNext(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Module name must be supplied."
+ );
+
+ final String name = moduleName.next();
+
+ RestconfValidationUtils.checkDocumentedError(
+ !name.isEmpty() && ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(name.charAt(0)),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Identifier must start with character from set 'a-zA-Z_"
+ );
+
+ RestconfValidationUtils.checkDocumentedError(
+ !name.toUpperCase().startsWith("XML"),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Identifier must NOT start with XML ignore case."
+ );
+
+ RestconfValidationUtils.checkDocumentedError(
+ ParserBuilderConstants.Deserializer.IDENTIFIER.matchesAllOf(name.substring(1)),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ "Supplied name has not expected identifier format."
+ );
+
+ return name;
+ }
+
+}