<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
*/
package org.opendaylight.controller.sal.rest.api;
+import org.opendaylight.yangtools.yang.common.QName;
+
public class Draft02 {
- public static class MediaTypes {
- public static final String API = "application/yang.api";
- public static final String DATASTORE = "application/yang.datastore";
- public static final String DATA = "application/yang.data";
- public static final String OPERATION = "application/yang.operation";
- public static final String PATCH = "application/yang.patch";
- public static final String PATCH_STATUS = "application/yang.patch-status";
- public static final String STREAM = "application/yang.stream";
+ public static interface MediaTypes {
+ String API = "application/yang.api";
+ String DATASTORE = "application/yang.datastore";
+ String DATA = "application/yang.data";
+ String OPERATION = "application/yang.operation";
+ String PATCH = "application/yang.patch";
+ String PATCH_STATUS = "application/yang.patch-status";
+ String STREAM = "application/yang.stream";
+ }
+
+ public static interface RestConfModule {
+ String REVISION = "2013-10-19";
+
+ String NAME = "ietf-restconf";
+
+ String NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf";
+
+ String RESTCONF_GROUPING_SCHEMA_NODE = "restconf";
+
+ String RESTCONF_CONTAINER_SCHEMA_NODE = "restconf";
+
+ String MODULES_CONTAINER_SCHEMA_NODE = "modules";
+
+ String MODULE_LIST_SCHEMA_NODE = "module";
+
+ String STREAMS_CONTAINER_SCHEMA_NODE = "streams";
+
+ String STREAM_LIST_SCHEMA_NODE = "stream";
+
+ String OPERATIONS_CONTAINER_SCHEMA_NODE = "operations";
+
+ String ERRORS_GROUPING_SCHEMA_NODE = "errors";
+
+ String ERRORS_CONTAINER_SCHEMA_NODE = "errors";
+
+ String ERROR_LIST_SCHEMA_NODE = "error";
+
+ QName IETF_RESTCONF_QNAME = QName.create( Draft02.RestConfModule.NAMESPACE,
+ Draft02.RestConfModule.REVISION,
+ Draft02.RestConfModule.NAME );
+
+ QName ERRORS_CONTAINER_QNAME = QName.create( IETF_RESTCONF_QNAME, ERRORS_CONTAINER_SCHEMA_NODE );
+
+ QName ERROR_LIST_QNAME = QName.create( IETF_RESTCONF_QNAME, ERROR_LIST_SCHEMA_NODE );
+
+ QName ERROR_TYPE_QNAME = QName.create( IETF_RESTCONF_QNAME, "error-type" );
+
+ QName ERROR_TAG_QNAME = QName.create( IETF_RESTCONF_QNAME, "error-tag" );
+
+ QName ERROR_APP_TAG_QNAME = QName.create( IETF_RESTCONF_QNAME, "error-app-tag" );
+
+ QName ERROR_MESSAGE_QNAME = QName.create( IETF_RESTCONF_QNAME, "error-message" );
+
+ QName ERROR_INFO_QNAME = QName.create( IETF_RESTCONF_QNAME, "error-info" );
}
-
- public static class Paths {
-
+
+
+ public static interface Paths {
+
}
}
import java.io.IOException;
import java.net.URI;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
private void writeChildrenOfParent(final JsonWriter writer, final CompositeNode parent, final DataNodeContainer parentSchema)
throws IOException {
checkNotNull(parent);
- checkNotNull(parentSchema);
+
+ Set<DataSchemaNode> parentSchemaChildNodes = parentSchema == null ?
+ Collections.<DataSchemaNode>emptySet() : parentSchema.getChildNodes();
+
for (Node<?> child : parent.getValue()) {
- DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
+ DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchemaChildNodes);
if (childSchema == null) {
- throw new UnsupportedDataTypeException("Probably the data node \"" + child.getNodeType().getLocalName()
- + "\" is not conform to schema");
- }
+ // Node may not conform to schema or allows "anyxml" - we'll process it.
- if (childSchema instanceof ContainerSchemaNode) {
+ logger.debug( "No schema found for data node \"" + child.getNodeType() );
+
+ handleNoSchemaFound( writer, child, parent );
+ }
+ else if (childSchema instanceof ContainerSchemaNode) {
Preconditions.checkState(child instanceof CompositeNode,
"Data representation of Container should be CompositeNode - " + child.getNodeType());
writeContainer(writer, (CompositeNode) child, (ContainerSchemaNode) childSchema);
}
for (Node<?> child : parent.getValue()) {
- DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
+ DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchemaChildNodes);
if (childSchema instanceof LeafListSchemaNode) {
foundLeafLists.remove(childSchema);
} else if (childSchema instanceof ListSchemaNode) {
}
}
+ private void handleNoSchemaFound( final JsonWriter writer, final Node<?> node,
+ final CompositeNode parent ) throws IOException {
+ if( node instanceof SimpleNode<?> ) {
+ writeName( node, null, writer );
+ Object value = node.getValue();
+ if( value != null ) {
+ writer.value( String.valueOf( value ) );
+ }
+ } else { // CompositeNode
+ Preconditions.checkState( node instanceof CompositeNode,
+ "Data representation of Container should be CompositeNode - " + node.getNodeType() );
+
+ writeContainer( writer, (CompositeNode) node, null );
+ }
+ }
+
private DataSchemaNode findFirstSchemaForNode(final Node<?> node, final Set<DataSchemaNode> dataSchemaNode) {
for (DataSchemaNode dsn : dataSchemaNode) {
if (node.getNodeType().equals(dsn.getQName())) {
private void writeName(final Node<?> node, final DataSchemaNode schema, final JsonWriter writer) throws IOException {
String nameForOutput = node.getNodeType().getLocalName();
- if (schema.isAugmenting()) {
+ if ( schema != null && schema.isAugmenting()) {
ControllerContext contContext = ControllerContext.getInstance();
CharSequence moduleName = null;
if (mountPoint == null) {
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Provider
@Consumes({ Draft02.MediaTypes.DATA + RestconfService.JSON, Draft02.MediaTypes.OPERATION + RestconfService.JSON,
public enum JsonToCompositeNodeProvider implements MessageBodyReader<CompositeNode> {
INSTANCE;
+ private final static Logger LOG = LoggerFactory.getLogger( JsonToCompositeNodeProvider.class );
+
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
JsonReader jsonReader = new JsonReader();
try {
return jsonReader.read(entityStream);
- } catch (UnsupportedFormatException e) {
- throw new WebApplicationException(e, Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage())
- .build());
+ } catch (Exception e) {
+ LOG.debug( "Error parsing json input", e );
+ throw new RestconfDocumentedException(
+ "Error parsing input: " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE );
}
}
import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
+import com.google.common.collect.ImmutableSet;
+
public class RestconfApplication extends Application {
+ @Override
+ public Set<Class<?>> getClasses() {
+ return ImmutableSet.<Class<?>>of( RestconfDocumentedExceptionMapper.class );
+ }
+
@Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
return singletons;
}
+
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.sal.rest.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.Map.Entry;
+
+import javax.activation.UnsupportedDataTypeException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.*;
+
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
+import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by
+ * resource implementations and translates appropriately to restconf error response as defined in
+ * the RESTCONF RFC draft.
+ *
+ * @author Thomas Pantelis
+ */
+@Provider
+public class RestconfDocumentedExceptionMapper implements ExceptionMapper<RestconfDocumentedException> {
+
+ private final static Logger LOG = LoggerFactory.getLogger( RestconfDocumentedExceptionMapper.class );
+
+ @Context
+ private HttpHeaders headers;
+
+ @Override
+ public Response toResponse( RestconfDocumentedException exception ) {
+
+ LOG.debug( "In toResponse: {}", exception.getMessage() );
+
+ // Default to the content type if there's no Accept header
+
+ MediaType mediaType = headers.getMediaType();
+
+ List<MediaType> accepts = headers.getAcceptableMediaTypes();
+
+ LOG.debug( "Accept headers: {}", accepts );
+
+ if( accepts != null && accepts.size() > 0 ) {
+ mediaType = accepts.get( 0 ); // just pick the first one
+ }
+
+ LOG.debug( "Using MediaType: {}", mediaType );
+
+ List<RestconfError> errors = exception.getErrors();
+ if( errors.isEmpty() ) {
+ // We don't actually want to send any content but, if we don't set any content here,
+ // the tomcat front-end will send back an html error report. To prevent that, set a
+ // single space char in the entity.
+
+ return Response.status( exception.getStatus() )
+ .type( MediaType.TEXT_PLAIN_TYPE )
+ .entity( " " ).build();
+ }
+
+ Status status = errors.iterator().next().getErrorTag().getStatusCode();
+
+ ControllerContext context = ControllerContext.getInstance();
+ DataNodeContainer errorsSchemaNode = (DataNodeContainer)context.getRestconfModuleErrorsSchemaNode();
+
+ if( errorsSchemaNode == null ) {
+ return Response.status( status )
+ .type( MediaType.TEXT_PLAIN_TYPE )
+ .entity( exception.getMessage() ).build();
+ }
+
+ ImmutableList.Builder<Node<?>> errorNodes = ImmutableList.<Node<?>> builder();
+ for( RestconfError error: errors ) {
+ errorNodes.add( toDomNode( error ) );
+ }
+
+ ImmutableCompositeNode errorsNode =
+ ImmutableCompositeNode.create( ERRORS_CONTAINER_QNAME, errorNodes.build() );
+
+ Object responseBody;
+ if( mediaType.getSubtype().endsWith( "json" ) ) {
+ responseBody = toJsonResponseBody( errorsNode, errorsSchemaNode );
+ }
+ else {
+ responseBody = toXMLResponseBody( errorsNode, errorsSchemaNode );
+ }
+
+ return Response.status( status ).type( mediaType ).entity( responseBody ).build();
+ }
+
+ private Object toJsonResponseBody( ImmutableCompositeNode errorsNode,
+ DataNodeContainer errorsSchemaNode ) {
+
+ JsonMapper jsonMapper = new JsonMapper();
+
+ Object responseBody = null;
+ try {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ JsonWriter writer = new JsonWriter( new OutputStreamWriter( outStream, "UTF-8" ) );
+ writer.setIndent( " " );
+
+ jsonMapper.write( writer, errorsNode, errorsSchemaNode, null );
+ writer.flush();
+
+ responseBody = outStream.toString( "UTF-8" );
+ }
+ catch( IOException e ) {
+ LOG.error( "Error writing error response body", e );
+ }
+
+ return responseBody;
+ }
+
+ private Object toXMLResponseBody( ImmutableCompositeNode errorsNode,
+ DataNodeContainer errorsSchemaNode ) {
+
+ XmlMapper xmlMapper = new XmlMapper();
+
+ Object responseBody = null;
+ try {
+ Document xmlDoc = xmlMapper.write( errorsNode, errorsSchemaNode );
+
+ responseBody = documentToString( xmlDoc );
+ }
+ catch( TransformerException | UnsupportedDataTypeException | UnsupportedEncodingException e ) {
+ LOG.error( "Error writing error response body", e );
+ }
+
+ return responseBody;
+ }
+
+ private String documentToString( Document doc ) throws TransformerException, UnsupportedEncodingException {
+ Transformer transformer = createTransformer();
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+
+ transformer.transform( new DOMSource( doc ), new StreamResult( outStream ) );
+
+ return outStream.toString( "UTF-8" );
+ }
+
+ private Transformer createTransformer() throws TransformerFactoryConfigurationError,
+ TransformerConfigurationException {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ 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" );
+ return transformer;
+ }
+
+ private Node<?> toDomNode( RestconfError error ) {
+
+ CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+ builder.setQName( ERROR_LIST_QNAME );
+
+ addLeaf( builder, ERROR_TYPE_QNAME, error.getErrorType().getErrorTypeTag() );
+ addLeaf( builder, ERROR_TAG_QNAME, error.getErrorTag().getTagValue() );
+ addLeaf( builder, ERROR_MESSAGE_QNAME, error.getErrorMessage() );
+ addLeaf( builder, ERROR_APP_TAG_QNAME, error.getErrorAppTag() );
+
+ Node<?> errorInfoNode = parseErrorInfo( error.getErrorInfo() );
+ if( errorInfoNode != null ) {
+ builder.add( errorInfoNode );
+ }
+
+ return builder.toInstance();
+ }
+
+ private Node<?> parseErrorInfo( String errorInfo ) {
+ if( Strings.isNullOrEmpty( errorInfo ) ) {
+ return null;
+ }
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware( true );
+ factory.setCoalescing( true );
+ factory.setIgnoringElementContentWhitespace( true );
+ factory.setIgnoringComments( true );
+
+ // Wrap the error info content in a root <error-info> element so it can be parsed
+ // as XML. The error info content may or may not be XML. If not then it will be
+ // parsed as text content of the <error-info> element.
+
+ String errorInfoWithRoot =
+ new StringBuilder( "<error-info xmlns=\"" ).append( NAMESPACE ).append( "\">" )
+ .append( errorInfo ).append( "</error-info>" ).toString();
+
+ Document doc = null;
+ try {
+ doc = factory.newDocumentBuilder().parse(
+ new InputSource( new StringReader( errorInfoWithRoot ) ) );
+ }
+ catch( Exception e ) {
+ // TODO: what if the content is text that happens to contain invalid markup? Could
+ // wrap in CDATA and try again.
+
+ LOG.warn( "Error parsing restconf error-info, \"" + errorInfo + "\", as XML: " +
+ e.toString() );
+ return null;
+ }
+
+ Node<?> errorInfoNode = XmlDocumentUtils.toDomNode( doc );
+
+ if( errorInfoNode instanceof CompositeNode ) {
+ CompositeNode compositeNode = (CompositeNode)XmlDocumentUtils.toDomNode( doc );
+
+ // At this point the QName for the "error-info" CompositeNode doesn't contain the revision
+ // as it isn't present in the XML. So we'll copy all the child nodes and create a new
+ // CompositeNode with the full QName. This is done so the XML/JSON mapping code can
+ // locate the schema.
+
+ ImmutableList.Builder<Node<?>> childNodes = ImmutableList.builder();
+ for( Entry<QName, List<Node<?>>> entry: compositeNode.entrySet() ) {
+ childNodes.addAll( entry.getValue() );
+ }
+
+ errorInfoNode = ImmutableCompositeNode.create( ERROR_INFO_QNAME, childNodes.build() );
+ }
+
+ return errorInfoNode;
+ }
+
+ private void addLeaf( CompositeNodeBuilder<ImmutableCompositeNode> builder, QName qname,
+ String value ) {
+ if( !Strings.isNullOrEmpty( value ) ) {
+ builder.addLeaf( qname, value );
+ }
+ }
+}
import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.api.RestconfService;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.controller.sal.restconf.impl.StructuredData;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
- return true;
+ return type.equals( StructuredData.class );
}
@Override
throws IOException, WebApplicationException {
CompositeNode data = t.getData();
if (data == null) {
- throw new ResponseException(Response.Status.NOT_FOUND, "No data exists.");
+ throw new RestconfDocumentedException(Response.Status.NOT_FOUND);
}
JsonWriter writer = new JsonWriter(new OutputStreamWriter(entityStream, "UTF-8"));
import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.api.RestconfService;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.controller.sal.restconf.impl.StructuredData;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
- return true;
+ return type.equals( StructuredData.class );
}
@Override
throws IOException, WebApplicationException {
CompositeNode data = t.getData();
if (data == null) {
- throw new ResponseException(Response.Status.NOT_FOUND, "No data exists.");
+ throw new RestconfDocumentedException(Response.Status.NOT_FOUND);
}
-
+
XmlMapper xmlMapper = new XmlMapper();
Document domTree = xmlMapper.write(data, (DataNodeContainer) t.getSchema());
try {
transformer.transform(new DOMSource(domTree), new StreamResult(entityStream));
} catch (TransformerException e) {
logger.error("Error during translation of Document to OutputStream", e);
- throw new ResponseException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
+ throw new RestconfDocumentedException( e.getMessage(), ErrorType.TRANSPORT,
+ ErrorTag.OPERATION_FAILED );
}
}
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.xml.stream.XMLStreamException;
import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.api.RestconfService;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Provider
@Consumes({ Draft02.MediaTypes.DATA + RestconfService.XML, Draft02.MediaTypes.OPERATION + RestconfService.XML,
public enum XmlToCompositeNodeProvider implements MessageBodyReader<CompositeNode> {
INSTANCE;
+ private final static Logger LOG = LoggerFactory.getLogger( XmlToCompositeNodeProvider.class );
+
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
try {
return xmlReader.read(entityStream);
} catch (XMLStreamException | UnsupportedFormatException e) {
- throw new ResponseException(Response.Status.BAD_REQUEST, e.getMessage());
+ LOG.debug( "Error parsing json input", e );
+ throw new RestconfDocumentedException(
+ "Error parsing input: " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE );
}
}
import org.opendaylight.controller.sal.core.api.data.DataChangeListener;
import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction;
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
-import org.opendaylight.controller.sal.rest.impl.RestconfProvider;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.common.QName;
private void checkPreconditions() {
if( context == null || dataService == null ) {
- ResponseException _responseException = new ResponseException( Status.SERVICE_UNAVAILABLE,
- RestconfProvider.NOT_INITALIZED_MSG );
- throw _responseException;
+ throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
}
}
return mountPoint.readOperationalData( path );
}
- public RpcResult<CompositeNode> invokeRpc( final QName type, final CompositeNode payload ) {
+ public Future<RpcResult<CompositeNode>> invokeRpc( final QName type, final CompositeNode payload ) {
this.checkPreconditions();
- final Future<RpcResult<CompositeNode>> future = context.rpc( type, payload );
-
- try {
- return future.get();
- }
- catch( Exception e ) {
- throw new ResponseException( e, "Error invoking RPC " + type );
- }
+ return context.rpc( type, payload );
}
public Future<RpcResult<TransactionStatus>> commitConfigurationDataPut( final InstanceIdentifier path,
if (availableNode != null) {
String errMsg = "Post Configuration via Restconf was not executed because data already exists";
BrokerFacade.LOG.warn((new StringBuilder(errMsg)).append(" : ").append(path).toString());
- // FIXME: return correct ietf-restconf:errors -> follow specification
- // (http://tools.ietf.org/html/draft-bierman-netconf-restconf-03#page-48)
- throw new ResponseException(Status.CONFLICT, errMsg);
+
+ throw new RestconfDocumentedException(
+ "Data already exists for path: " + path, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS );
}
BrokerFacade.LOG.trace( "Post Configuration via Restconf: {}", path );
transaction.putConfigurationData( path, payload );
if (availableNode != null) {
String errMsg = "Post Configuration via Restconf was not executed because data already exists";
BrokerFacade.LOG.warn((new StringBuilder(errMsg)).append(" : ").append(path).toString());
- // FIXME: return correct ietf-restconf:errors -> follow specification
- // (http://tools.ietf.org/html/draft-bierman-netconf-restconf-03#page-48)
- throw new ResponseException(Status.CONFLICT, errMsg);
+
+ throw new RestconfDocumentedException(
+ "Data already exists for path: " + path, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS );
}
BrokerFacade.LOG.trace( "Post Configuration via Restconf: {}", path );
transaction.putConfigurationData( path, payload );
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
import org.opendaylight.controller.sal.core.api.mount.MountService;
+import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.impl.RestUtil;
-import org.opendaylight.controller.sal.rest.impl.RestconfProvider;
import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
import org.opendaylight.controller.sal.restconf.impl.RestCodec;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
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.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;
private void checkPreconditions() {
if( globalSchema == null ) {
- throw new ResponseException( Status.SERVICE_UNAVAILABLE, RestconfProvider.NOT_INITALIZED_MSG );
+ throw new RestconfDocumentedException( Status.SERVICE_UNAVAILABLE );
}
}
String first = pathArgs.iterator().next();
final String startModule = ControllerContext.toModuleName( first );
if( startModule == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "First node in URI has to be in format \"moduleName:nodeName\"" );
+ throw new RestconfDocumentedException(
+ "First node in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
InstanceIdentifierBuilder builder = InstanceIdentifier.builder();
latestModule, null, toMountPointIdentifier );
if( iiWithSchemaNode == null ) {
- throw new ResponseException( Status.BAD_REQUEST, "URI has bad format" );
+ throw new RestconfDocumentedException(
+ "URI has bad format", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
return iiWithSchemaNode;
return builder.toString();
}
+ public Module getRestconfModule() {
+ return findModuleByNameAndRevision( Draft02.RestConfModule.IETF_RESTCONF_QNAME );
+ }
+
+ public DataSchemaNode getRestconfModuleErrorsSchemaNode() {
+ Module restconfModule = getRestconfModule();
+ if( restconfModule == null ) {
+ return null;
+ }
+
+ Set<GroupingDefinition> groupings = restconfModule.getGroupings();
+
+ final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
+ @Override
+ public boolean apply(final GroupingDefinition g) {
+ return Objects.equal(g.getQName().getLocalName(),
+ Draft02.RestConfModule.ERRORS_GROUPING_SCHEMA_NODE);
+ }
+ };
+
+ Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
+
+ final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
+
+ List<DataSchemaNode> instanceDataChildrenByName =
+ this.findInstanceDataChildrenByName(restconfGrouping,
+ Draft02.RestConfModule.ERRORS_CONTAINER_SCHEMA_NODE);
+ return Iterables.getFirst(instanceDataChildrenByName, null);
+ }
+
+ public DataSchemaNode getRestconfModuleRestConfSchemaNode( Module inRestconfModule,
+ String schemaNodeName ) {
+ Module restconfModule = inRestconfModule;
+ if( restconfModule == null ) {
+ restconfModule = getRestconfModule();
+ }
+
+ if( restconfModule == null ) {
+ return null;
+ }
+
+ Set<GroupingDefinition> groupings = restconfModule.getGroupings();
+
+ final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
+ @Override
+ public boolean apply(final GroupingDefinition g) {
+ return Objects.equal(g.getQName().getLocalName(),
+ Draft02.RestConfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ }
+ };
+
+ Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
+
+ final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
+
+ List<DataSchemaNode> instanceDataChildrenByName =
+ this.findInstanceDataChildrenByName(restconfGrouping,
+ Draft02.RestConfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
+
+ if (Objects.equal(schemaNodeName, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+ else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+ else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = Iterables.getFirst(instances, null);
+ instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
+ Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+ else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+ else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modules = Iterables.getFirst(instances, null);
+ instances = this.findInstanceDataChildrenByName(((DataNodeContainer) modules),
+ Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+ else if(Objects.equal(schemaNodeName, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE)) {
+ List<DataSchemaNode> instances =
+ this.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
+ Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ return Iterables.getFirst(instances, null);
+ }
+
+ return null;
+ }
+
private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) {
for( final ChoiceCaseNode caze : container.getCases() ) {
final DataSchemaNode ret = ControllerContext.childByQName( caze, name );
if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) &&
Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) {
if( mountPoint != null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "Restconf supports just one mount point in URI." );
+ throw new RestconfDocumentedException(
+ "Restconf supports just one mount point in URI.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
}
if( mountService == null ) {
- throw new ResponseException( Status.SERVICE_UNAVAILABLE,
- "MountService was not found. Finding behind mount points does not work." );
+ throw new RestconfDocumentedException(
+ "MountService was not found. Finding behind mount points does not work.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED );
}
final InstanceIdentifier partialPath = builder.toInstance();
final MountInstance mount = mountService.getMountPoint( partialPath );
if( mount == null ) {
LOG.debug( "Instance identifier to missing mount point: {}", partialPath );
- throw new ResponseException( Status.BAD_REQUEST,
- "Mount point does not exist." );
+ throw new RestconfDocumentedException(
+ "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
final SchemaContext mountPointSchema = mount.getSchemaContext();
if( mountPointSchema == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "Mount point does not contain any schema with modules." );
+ throw new RestconfDocumentedException(
+ "Mount point does not contain any schema with modules.",
+ ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
}
if( returnJustMountPoint ) {
final String moduleNameBehindMountPoint = toModuleName( strings.get( 1 ) );
if( moduleNameBehindMountPoint == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "First node after mount point in URI has to be in format \"moduleName:nodeName\"" );
+ throw new RestconfDocumentedException(
+ "First node after mount point in URI has to be in format \"moduleName:nodeName\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema,
moduleNameBehindMountPoint );
if( moduleBehindMountPoint == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. \"" + moduleName +
- "\" module does not exist in mount point." );
+ throw new RestconfDocumentedException(
+ "\"" +moduleName + "\" module does not exist in mount point.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
List<String> subList = strings.subList( 1, strings.size() );
if( mountPoint == null ) {
module = this.getLatestModule( globalSchema, moduleName );
if( module == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. \"" + moduleName + "\" module does not exist." );
+ throw new RestconfDocumentedException(
+ "\"" + moduleName + "\" module does not exist.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
}
else {
module = schemaContext == null ? null :
this.getLatestModule( schemaContext, moduleName );
if( module == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. \"" + moduleName +
- "\" module does not exist in mount point." );
+ throw new RestconfDocumentedException(
+ "\"" + moduleName + "\" module does not exist in mount point.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
}
targetNode = this.findInstanceDataChildByNameAndNamespace(
parentNode, nodeName, module.getNamespace() );;
if( targetNode == null ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. Possible reasons:\n" +
- "1. \"" + head + "\" was not found in parent data node.\n" +
- "2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
- MOUNT + "/" + head + "\"." );
+ throw new RestconfDocumentedException(
+ "URI has bad format. Possible reasons:\n" +
+ " 1. \"" + head + "\" was not found in parent data node.\n" +
+ " 2. \"" + head + "\" is behind mount point. Then it should be in format \"/" +
+ MOUNT + "/" + head + "\".", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
} else {
final List<DataSchemaNode> potentialSchemaNodes =
.append( "\n" );
}
- throw new ResponseException( Status.BAD_REQUEST,
+ throw new RestconfDocumentedException(
"URI has bad format. Node \"" + nodeName +
"\" is added as augment from more than one module. " +
"Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." +
"\nThe node is added as augment from modules with namespaces:\n" +
- strBuilder.toString() );
+ strBuilder.toString(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
if( potentialSchemaNodes.isEmpty() ) {
- throw new ResponseException( Status.BAD_REQUEST, "URI has bad format. \"" + nodeName +
- "\" was not found in parent data node.\n" );
+ throw new RestconfDocumentedException(
+ "\"" + nodeName + "\" in URI was not found in parent data node",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
targetNode = potentialSchemaNodes.iterator().next();
}
if( !this.isListOrContainer( targetNode ) ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. Node \"" + head +
- "\" must be Container or List yang type." );
+ throw new RestconfDocumentedException(
+ "URI has bad format. Node \"" + head + "\" must be Container or List yang type.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
int consumed = 1;
final ListSchemaNode listNode = ((ListSchemaNode) targetNode);
final int keysSize = listNode.getKeyDefinition().size();
if( (strings.size() - consumed) < keysSize ) {
- throw new ResponseException( Status.BAD_REQUEST, "Missing key for list \"" +
- listNode.getQName().getLocalName() + "\"." );
+ throw new RestconfDocumentedException(
+ "Missing key for list \"" + listNode.getQName().getLocalName() + "\".",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final List<String> uriKeyValues = strings.subList( consumed, consumed + keysSize );
{
final String uriKeyValue = uriKeyValues.get( i );
if( uriKeyValue.equals( NULL_VALUE ) ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "URI has bad format. List \"" + listNode.getQName().getLocalName() +
- "\" cannot contain \"null\" value as a key." );
+ throw new RestconfDocumentedException(
+ "URI has bad format. List \"" + listNode.getQName().getLocalName() +
+ "\" cannot contain \"null\" value as a key.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
this.addKeyValue( keyValues, listNode.getDataChildByName( key ),
}
if( decoded == null ) {
- throw new ResponseException( Status.BAD_REQUEST, uriValue + " from URI can\'t be resolved. " +
- additionalInfo );
+ throw new RestconfDocumentedException(
+ uriValue + " from URI can't be resolved. " + additionalInfo,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
map.put( node.getQName(), decoded );
return decodedPathArgs;
}
catch( UnsupportedEncodingException e ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "Invalid URL path '" + strings + "': " + e.getMessage() );
+ throw new RestconfDocumentedException(
+ "Invalid URL path '" + strings + "': " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET );
}
catch( UnsupportedEncodingException e ) {
- throw new ResponseException( Status.BAD_REQUEST,
- "Invalid URL path arg '" + pathArg + "': " + e.getMessage() );
+ throw new RestconfDocumentedException(
+ "Invalid URL path arg '" + pathArg + "': " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
+++ /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.controller.sal.restconf.impl;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-
-public class ResponseException extends WebApplicationException {
-
- private static final long serialVersionUID = -5320114450593021655L;
-
- public ResponseException(Status status, String msg) {
- super(Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(msg).build());
- }
-
- public ResponseException(Throwable cause, String msg) {
- super(cause, Response.status(Status.INTERNAL_SERVER_ERROR).
- type(MediaType.TEXT_PLAIN_TYPE).entity(msg).build());
- }
-}
--- /dev/null
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.sal.restconf.impl;
+
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response.Status;
+
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Unchecked exception to communicate error information, as defined in the ietf restcong draft,
+ * to be sent to the client.
+ *
+ * @author Devin Avery
+ * @author Thomas Pantelis
+ * @see {@link https://tools.ietf.org/html/draft-bierman-netconf-restconf-02}
+ */
+public class RestconfDocumentedException extends WebApplicationException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<RestconfError> errors;
+ private final Status status;
+
+ /**
+ * Constructs an instance with an error message. The error type defaults to APPLICATION and
+ * the error tag defaults to OPERATION_FAILED.
+ *
+ * @param message A string which provides a plain text string describing the error.
+ */
+ public RestconfDocumentedException( String message ) {
+ this( message, RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED );
+ }
+
+ /**
+ * Constructs an instance with an error message, error type, and error tag.
+ *
+ * @param message A string which provides a plain text string describing the error.
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ */
+ public RestconfDocumentedException( String message, ErrorType errorType, ErrorTag errorTag ) {
+ this( null, new RestconfError( errorType, errorTag, message ) );
+ }
+
+ /**
+ * Constructs an instance with an error message and exception cause. The stack trace of the
+ * exception is included in the error info.
+ *
+ * @param message A string which provides a plain text string describing the error.
+ * @param cause The underlying exception cause.
+ */
+ public RestconfDocumentedException( String message, Throwable cause ) {
+ this( cause, new RestconfError( RestconfError.ErrorType.APPLICATION,
+ RestconfError.ErrorTag.OPERATION_FAILED, message,
+ null, RestconfError.toErrorInfo( cause ) ) );
+ }
+
+ /**
+ * Constructs an instance with the given error.
+ */
+ public RestconfDocumentedException( RestconfError error ) {
+ this( null, error );
+ }
+
+ /**
+ * Constructs an instance with the given errors.
+ */
+ public RestconfDocumentedException( List<RestconfError> errors ) {
+ this.errors = ImmutableList.copyOf( errors );
+ Preconditions.checkArgument( !this.errors.isEmpty(), "RestconfError list can't be empty" );
+ status = null;
+ }
+
+ /**
+ * Constructs an instance with an HTTP status and no error information.
+ *
+ * @param status the HTTP status.
+ */
+ public RestconfDocumentedException( Status status ) {
+ Preconditions.checkNotNull( status, "Status can't be null" );
+ errors = ImmutableList.of();
+ this.status = status;
+ }
+
+ private RestconfDocumentedException( Throwable cause, RestconfError error ) {
+ super( cause );
+ Preconditions.checkNotNull( error, "RestconfError can't be null" );
+ errors = ImmutableList.of( error );
+ status = null;
+ }
+
+ public List<RestconfError> getErrors() {
+ return errors;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+
+ @Override
+ public String getMessage() {
+ return "errors: " + errors + (status != null ? ", status: " + status : "");
+ }
+}
--- /dev/null
+/*
+* Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License v1.0 which accompanies this distribution,
+* and is available at http://www.eclipse.org/legal/epl-v10.html
+*/
+package org.opendaylight.controller.sal.restconf.impl;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.opendaylight.yangtools.yang.common.RpcError;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Encapsulates a restconf error as defined in the ietf restconf draft.
+ *
+ * <br><br><b>Note:</b> Enumerations defined within are provided by the ietf restconf draft.
+ *
+ * @author Devin Avery
+ * @see {@link https://tools.ietf.org/html/draft-bierman-netconf-restconf-02}
+ */
+public class RestconfError {
+
+ public static enum ErrorType {
+ /** Errors relating to the transport layer */
+ TRANSPORT,
+ /** Errors relating to the RPC or notification layer */
+ RPC,
+ /** Errors relating to the protocol operation layer. */
+ PROTOCOL,
+ /** Errors relating to the server application layer. */
+ APPLICATION;
+
+ public String getErrorTypeTag() {
+ return name().toLowerCase();
+ }
+
+ public static ErrorType valueOfCaseInsensitive( String value )
+ {
+ try {
+ return ErrorType.valueOf( ErrorType.class, value.toUpperCase() );
+ }
+ catch( IllegalArgumentException e ) {
+ return APPLICATION;
+ }
+ }
+ }
+
+ public static enum ErrorTag {
+ IN_USE( "in-use", Status.fromStatusCode(409)),
+ INVALID_VALUE( "invalid-value", Status.fromStatusCode(400)),
+ TOO_BIG( "too-big", Status.fromStatusCode(413)),
+ MISSING_ATTRIBUTE( "missing-attribute", Status.fromStatusCode(400)),
+ BAD_ATTRIBUTE( "bad-attribute", Status.fromStatusCode(400)),
+ UNKNOWN_ATTRIBUTE( "unknown-attribute", Status.fromStatusCode(400)),
+ BAD_ELEMENT( "bad-element", Status.fromStatusCode(400)),
+ UNKNOWN_ELEMENT( "unknown-element", Status.fromStatusCode(400)),
+ UNKNOWN_NAMESPACE( "unknown-namespace", Status.fromStatusCode(400)),
+ ACCESS_DENIED( "access-denied", Status.fromStatusCode(403)),
+ LOCK_DENIED( "lock-denied", Status.fromStatusCode(409)),
+ RESOURCE_DENIED( "resource-denied", Status.fromStatusCode(409)),
+ ROLLBACK_FAILED( "rollback-failed", Status.fromStatusCode(500)),
+ DATA_EXISTS( "data-exists", Status.fromStatusCode(409)),
+ DATA_MISSING( "data-missing", Status.fromStatusCode(409)),
+ OPERATION_NOT_SUPPORTED( "operation-not-supported", Status.fromStatusCode(501)),
+ OPERATION_FAILED( "operation-failed", Status.fromStatusCode(500)),
+ PARTIAL_OPERATION( "partial-operation", Status.fromStatusCode(500)),
+ MALFORMED_MESSAGE( "malformed-message", Status.fromStatusCode(400));
+
+ private final String tagValue;
+ private final Status statusCode;
+
+ ErrorTag(final String tagValue, final Status statusCode) {
+ this.tagValue = tagValue;
+ this.statusCode = statusCode;
+ }
+
+ public String getTagValue() {
+ return this.tagValue.toLowerCase();
+ }
+
+ public static ErrorTag valueOfCaseInsensitive( String value )
+ {
+ try {
+ return ErrorTag.valueOf( ErrorTag.class, value.toUpperCase().replaceAll( "-","_" ) );
+ }
+ catch( IllegalArgumentException e ) {
+ return OPERATION_FAILED;
+ }
+ }
+
+ public Status getStatusCode() {
+ return statusCode;
+ }
+ }
+
+ private final ErrorType errorType;
+ private final ErrorTag errorTag;
+ private final String errorInfo;
+ private final String errorAppTag;
+ private final String errorMessage;
+ //TODO: Add in the error-path concept as defined in the ietf draft.
+
+ static String toErrorInfo( Throwable cause ) {
+ StringWriter writer = new StringWriter();
+ cause.printStackTrace( new PrintWriter( writer ) );
+ return writer.toString();
+ }
+
+ /**
+ * Constructs a RestConfError
+ *
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @param errorMessage A string which provides a plain text string describing the error.
+ */
+ public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage) {
+ this( errorType, errorTag, errorMessage, null );
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @param errorMessage A string which provides a plain text string describing the error.
+ * @param errorAppTag A string which represents an application-specific error tag that further
+ * specifies the error cause.
+ */
+ public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage,
+ String errorAppTag) {
+ this( errorType, errorTag, errorMessage, errorAppTag, null );
+ }
+
+ /**
+ * Constructs a RestConfError object.
+ *
+ * @param errorType The enumerated type indicating the layer where the error occurred.
+ * @param errorTag The enumerated tag representing a more specific error cause.
+ * @param errorMessage A string which provides a plain text string describing the error.
+ * @param errorAppTag A string which represents an application-specific error tag that further
+ * specifies the error cause.
+ * @param errorInfo A string, <b>formatted as XML</b>, which contains additional error information.
+ */
+ public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage,
+ String errorAppTag, String errorInfo) {
+ Preconditions.checkNotNull( errorType, "Error type is required for RestConfError" );
+ Preconditions.checkNotNull( errorTag, "Error tag is required for RestConfError");
+ this.errorType = errorType;
+ this.errorTag = errorTag;
+ this.errorMessage = errorMessage;
+ this.errorAppTag = errorAppTag;
+ this.errorInfo = errorInfo;
+ }
+
+ /**
+ * Constructs a RestConfError object from an RpcError.
+ */
+ public RestconfError( RpcError rpcError ) {
+
+ this.errorType = rpcError.getErrorType() == null ? ErrorType.APPLICATION :
+ ErrorType.valueOfCaseInsensitive( rpcError.getErrorType().name() );
+
+ this.errorTag = rpcError.getTag() == null ? ErrorTag.OPERATION_FAILED :
+ ErrorTag.valueOfCaseInsensitive( rpcError.getTag().toString() );
+
+ this.errorMessage = rpcError.getMessage();
+ this.errorAppTag = rpcError.getApplicationTag();
+
+ String errorInfo = null;
+ if( rpcError.getInfo() == null ) {
+ if( rpcError.getCause() != null ) {
+ errorInfo = toErrorInfo( rpcError.getCause() );
+ }
+ else if( rpcError.getSeverity() != null ) {
+ errorInfo = "<severity>" + rpcError.getSeverity().toString().toLowerCase() +
+ "</severity>";
+ }
+ }
+ else {
+ errorInfo = rpcError.getInfo();
+ }
+
+ this.errorInfo = errorInfo;
+ }
+
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+
+ public ErrorTag getErrorTag() {
+ return errorTag;
+ }
+
+ public String getErrorInfo() {
+ return errorInfo;
+ }
+
+ public String getErrorAppTag() {
+ return errorAppTag;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ @Override
+ public String toString() {
+ return "error-type: " + errorType.getErrorTypeTag()
+ + ", error-tag: " + errorTag.getTagValue() + ", "
+ + (errorAppTag != null ? "error-app-tag: " + errorAppTag + ", " : "")
+ + (errorMessage != null ? "error-message: " + errorMessage : "")
+ + (errorInfo != null ? "error-info: " + errorInfo + ", " : "") + "]";
+ }
+
+}
\ No newline at end of file
*/
package org.opendaylight.controller.sal.restconf.impl;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import org.apache.commons.lang3.StringUtils;
import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
+import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.api.RestconfService;
import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor;
import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor;
import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor;
+import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
+import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
+import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.RestCodec;
+import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
+import org.opendaylight.controller.sal.restconf.impl.StructuredData;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.controller.sal.streams.listeners.Notificator;
import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
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.parser.builder.impl.ContainerSchemaNodeBuilder;
import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-@SuppressWarnings("all")
public class RestconfImpl implements RestconfService {
private final static RestconfImpl INSTANCE = new RestconfImpl();
private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
- private final static String RESTCONF_MODULE_DRAFT02_REVISION = "2013-10-19";
-
- private final static String RESTCONF_MODULE_DRAFT02_NAME = "ietf-restconf";
-
- private final static String RESTCONF_MODULE_DRAFT02_NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf";
-
- private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE = "restconf";
-
- private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE = "restconf";
-
- private final static String RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE = "modules";
-
- private final static String RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE = "module";
-
- private final static String RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE = "streams";
-
- private final static String RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE = "stream";
-
- private final static String RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations";
-
private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription";
final Module restconfModule = this.getRestconfModule();
final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
- final DataSchemaNode moduleSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE);
+ final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
Set<Module> allModules = this.controllerContext.getAllModules();
for (final Module module : allModules) {
modulesAsData.add(moduleCompositeNode);
}
- final DataSchemaNode modulesSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
QName qName = modulesSchemaNode.getQName();
final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
return new StructuredData(modulesNode, modulesSchemaNode, null);
final List<Node<?>> streamsAsData = new ArrayList<Node<?>>();
Module restconfModule = this.getRestconfModule();
- final DataSchemaNode streamSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE);
+ final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
for (final String streamName : availableStreams) {
streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode));
}
- final DataSchemaNode streamsSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode streamsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE);
QName qName = streamsSchemaNode.getQName();
final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData);
return new StructuredData(streamsNode, streamsSchemaNode, null);
modules = this.controllerContext.getAllModules(mountPoint);
}
else {
- throw new ResponseException(Status.BAD_REQUEST,
- "URI has bad format. If modules behind mount point should be showed, URI has to end with " +
- ControllerContext.MOUNT);
+ throw new RestconfDocumentedException(
+ "URI has bad format. If modules behind mount point should be showed, URI has to end with " +
+ ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final List<Node<?>> modulesAsData = new ArrayList<Node<?>>();
Module restconfModule = this.getRestconfModule();
- final DataSchemaNode moduleSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE);
+ final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
for (final Module module : modules) {
modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode));
}
- final DataSchemaNode modulesSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE);
QName qName = modulesSchemaNode.getQName();
final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData);
return new StructuredData(modulesNode, modulesSchemaNode, mountPoint);
}
if (module == null) {
- throw new ResponseException(Status.BAD_REQUEST,
+ throw new RestconfDocumentedException(
"Module with name '" + moduleNameAndRevision.getLocalName() + "' and revision '" +
- moduleNameAndRevision.getRevision() + "' was not found.");
+ moduleNameAndRevision.getRevision() + "' was not found.",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
Module restconfModule = this.getRestconfModule();
- final DataSchemaNode moduleSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE);
+ final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE);
final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode);
return new StructuredData(moduleNode, moduleSchemaNode, mountPoint);
}
modules = this.controllerContext.getAllModules(mountPoint);
}
else {
- throw new ResponseException(Status.BAD_REQUEST,
- "URI has bad format. If operations behind mount point should be showed, URI has to end with " +
- ControllerContext.MOUNT);
+ throw new RestconfDocumentedException(
+ "URI has bad format. If operations behind mount point should be showed, URI has to end with " +
+ ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
return this.operationsFromModulesToStructuredData(modules, mountPoint);
final MountInstance mountPoint) {
final List<Node<?>> operationsAsData = new ArrayList<Node<?>>();
Module restconfModule = this.getRestconfModule();
- final DataSchemaNode operationsSchemaNode =
- this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE);
+ final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode(
+ restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE);
QName qName = operationsSchemaNode.getQName();
SchemaPath path = operationsSchemaNode.getPath();
ContainerSchemaNodeBuilder containerSchemaNodeBuilder =
- new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, qName, path);
+ new ContainerSchemaNodeBuilder(Draft02.RestConfModule.NAME, 0, qName, path);
final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder;
for (final Module module : modules) {
Set<RpcDefinition> rpcs = module.getRpcs();
}
private Module getRestconfModule() {
- QName qName = QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION,
- RESTCONF_MODULE_DRAFT02_NAME);
- final Module restconfModule = this.controllerContext.findModuleByNameAndRevision(qName);
+ Module restconfModule = controllerContext.getRestconfModule();
if (restconfModule == null) {
- throw new ResponseException(Status.INTERNAL_SERVER_ERROR, "Restconf module was not found.");
+ throw new RestconfDocumentedException(
+ "ietf-restconf module was not found.", ErrorType.APPLICATION,
+ ErrorTag.OPERATION_NOT_SUPPORTED );
}
return restconfModule;
Iterable<String> split = splitter.split(moduleNameAndRevision);
final List<String> pathArgs = Lists.<String>newArrayList(split);
if (pathArgs.size() < 2) {
- throw new ResponseException(Status.BAD_REQUEST,
- "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'");
+ throw new RestconfDocumentedException(
+ "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
try {
return QName.create(null, moduleRevision, moduleName);
}
catch (ParseException e) {
- throw new ResponseException(Status.BAD_REQUEST, "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'");
+ throw new RestconfDocumentedException(
+ "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues);
}
- private DataSchemaNode getSchemaNode(final Module restconfModule, final String schemaNodeName) {
- Set<GroupingDefinition> groupings = restconfModule.getGroupings();
-
- final Predicate<GroupingDefinition> filter = new Predicate<GroupingDefinition>() {
- @Override
- public boolean apply(final GroupingDefinition g) {
- return Objects.equal(g.getQName().getLocalName(),
- RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE);
- }
- };
-
- Iterable<GroupingDefinition> filteredGroups = Iterables.filter(groupings, filter);
-
- final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null);
-
- List<DataSchemaNode> instanceDataChildrenByName =
- this.controllerContext.findInstanceDataChildrenByName(restconfGrouping,
- RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE);
- final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null);
-
- if (Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE)) {
- List<DataSchemaNode> instances =
- this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
- RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE);
- return Iterables.getFirst(instances, null);
- }
- else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE)) {
- List<DataSchemaNode> instances =
- this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
- RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE);
- return Iterables.getFirst(instances, null);
- }
- else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE)) {
- List<DataSchemaNode> instances =
- this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
- RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE);
- final DataSchemaNode modules = Iterables.getFirst(instances, null);
- instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules),
- RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE);
- return Iterables.getFirst(instances, null);
- }
- else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)) {
- List<DataSchemaNode> instances =
- this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
- RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE);
- return Iterables.getFirst(instances, null);
- }
- else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)) {
- List<DataSchemaNode> instances =
- this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer),
- RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE);
- final DataSchemaNode modules = Iterables.getFirst(instances, null);
- instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules),
- RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE);
- return Iterables.getFirst(instances, null);
- }
-
- return null;
- }
-
@Override
public Object getRoot() {
return null;
final Object pathValue = pathNode == null ? null : pathNode.getValue();
if (!(pathValue instanceof InstanceIdentifier)) {
- throw new ResponseException(Status.INTERNAL_SERVER_ERROR,
- "Instance identifier was not normalized correctly.");
+ throw new RestconfDocumentedException(
+ "Instance identifier was not normalized correctly.",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED );
}
final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue);
}
if (Strings.isNullOrEmpty(streamName)) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Path is empty or contains data node which is not Container or List build-in type.");
+ throw new RestconfDocumentedException(
+ "Path is empty or contains data node which is not Container or List build-in type.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final SimpleNode<String> streamNameNode = NodeFactory.<String>createImmutableSimpleNode(
@Override
public StructuredData invokeRpc(final String identifier, final String noPayload) {
if (StringUtils.isNotBlank(noPayload)) {
- throw new ResponseException(
- Status.UNSUPPORTED_MEDIA_TYPE, "Content-Type contains unsupported Media Type.");
+ throw new RestconfDocumentedException(
+ "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final RpcExecutor rpc = resolveIdentifierInInvokeRpc(identifier);
return callRpc(rpc, null);
.format("Identifier %n%s%ncan\'t contain slash "
+ "character (/).%nIf slash is part of identifier name then use %%2F placeholder.",
identifier);
- throw new ResponseException(Status.NOT_FOUND, slashErrorMsg);
+ throw new RestconfDocumentedException(
+ slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
} else {
identifierEncoded = identifier;
}
RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded);
if (rpc == null) {
- throw new ResponseException(Status.NOT_FOUND, "RPC does not exist.");
+ throw new RestconfDocumentedException(
+ "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
}
if (mountPoint == null) {
private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload) {
if (rpcExecutor == null) {
- throw new ResponseException(Status.NOT_FOUND, "RPC does not exist.");
+ throw new RestconfDocumentedException(
+ "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT );
}
CompositeNode rpcRequest = null;
private void checkRpcSuccessAndThrowException(RpcResult<CompositeNode> rpcResult) {
if (rpcResult.isSuccessful() == false) {
- //TODO: Get smart about what error code we are return (Future Bug coming)
- throw new ResponseException(Status.INTERNAL_SERVER_ERROR,
- "The operation was not successful and there were no RPC errors returned");
+
+ Collection<RpcError> rpcErrors = rpcResult.getErrors();
+ if( rpcErrors == null || rpcErrors.isEmpty() ) {
+ throw new RestconfDocumentedException(
+ "The operation was not successful and there were no RPC errors returned",
+ ErrorType.RPC, ErrorTag.OPERATION_FAILED );
+ }
+
+ List<RestconfError> errorList = Lists.newArrayList();
+ for( RpcError rpcError: rpcErrors ) {
+ errorList.add( new RestconfError( rpcError ) );
+ }
+
+ throw new RestconfDocumentedException( errorList );
}
}
}
}
catch( Exception e ) {
- throw new ResponseException( e, "Error updating data" );
+ throw new RestconfDocumentedException( "Error updating data", e );
}
if( status.getResult() == TransactionStatus.COMMITED )
public Response createConfigurationData(final String identifier, final CompositeNode payload) {
URI payloadNS = this.namespace(payload);
if (payloadNS == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
+ throw new RestconfDocumentedException(
+ "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
}
InstanceIdWithSchemaNode iiWithData = null;
// payload represents mount point data and URI represents path to the mount point
if (this.endsWithMountPoint(identifier)) {
- throw new ResponseException(Status.BAD_REQUEST,
- "URI has bad format. URI should be without \"" + ControllerContext.MOUNT +
- "\" for POST operation.");
+ throw new RestconfDocumentedException(
+ "URI has bad format. URI should be without \"" + ControllerContext.MOUNT +
+ "\" for POST operation.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final String completeIdentifier = this.addMountPointIdentifier(identifier);
MountInstance mountPoint = incompleteInstIdWithData.getMountPoint();
final Module module = this.findModule(mountPoint, payload);
if (module == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Module was not found for \"" + payloadNS + "\"");
+ throw new RestconfDocumentedException(
+ "Module was not found for \"" + payloadNS + "\"",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
String payloadName = this.getName(payload);
status = future == null ? null : future.get();
}
}
- catch( ResponseException e ){ throw e; }
catch( Exception e ) {
- throw new ResponseException( e, "Error creating data" );
+ throw new RestconfDocumentedException( "Error creating data", e );
}
if (status == null) {
public Response createConfigurationData(final CompositeNode payload) {
URI payloadNS = this.namespace(payload);
if (payloadNS == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
+ throw new RestconfDocumentedException(
+ "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
}
final Module module = this.findModule(null, payload);
if (module == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)");
+ throw new RestconfDocumentedException(
+ "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)",
+ ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE );
}
String payloadName = this.getName(payload);
status = future == null ? null : future.get();
}
}
- catch( ResponseException e ){ throw e; }
catch( Exception e ) {
- throw new ResponseException( e, "Error creating data" );
+ throw new RestconfDocumentedException( "Error creating data", e );
}
if (status == null) {
}
}
catch( Exception e ) {
- throw new ResponseException( e, "Error creating data" );
+ throw new RestconfDocumentedException( "Error creating data", e );
}
if( status.getResult() == TransactionStatus.COMMITED )
public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
final String streamName = Notificator.createStreamNameFromUri(identifier);
if (Strings.isNullOrEmpty(streamName)) {
- throw new ResponseException(Status.BAD_REQUEST, "Stream name is empty.");
+ throw new RestconfDocumentedException(
+ "Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
final ListenerAdapter listener = Notificator.getListenerFor(streamName);
if (listener == null) {
- throw new ResponseException(Status.BAD_REQUEST, "Stream was not found.");
+ throw new RestconfDocumentedException(
+ "Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT );
}
broker.registerToListenDataChanges(listener);
}
if (dataNodeKeyValueObject == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Data contains list \"" + dataNode.getNodeType().getLocalName() +
- "\" which does not contain key: \"" + key.getLocalName() + "\"");
+ throw new RestconfDocumentedException(
+ "Data contains list \"" + dataNode.getNodeType().getLocalName() +
+ "\" which does not contain key: \"" + key.getLocalName() + "\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
keyValues.put(key, dataNodeKeyValueObject);
if (schema == null) {
QName nodeType = node == null ? null : node.getNodeType();
String localName = nodeType == null ? null : nodeType.getLocalName();
- String _plus = ("Data schema node was not found for " + localName);
- throw new ResponseException(Status.INTERNAL_SERVER_ERROR,
- "Data schema node was not found for " + localName );
+
+ throw new RestconfDocumentedException(
+ "Data schema node was not found for " + localName,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
if (!(schema instanceof DataNodeContainer)) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Root element has to be container or list yang datatype.");
+ throw new RestconfDocumentedException(
+ "Root element has to be container or list yang datatype.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
if ((node instanceof CompositeNodeWrapper)) {
try {
this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint);
}
- catch (NumberFormatException e) {
- throw new ResponseException(Status.BAD_REQUEST, e.getMessage());
+ catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException(
+ e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
final DataSchemaNode schema, final QName previousAugment,
final MountInstance mountPoint) {
if (schema == null) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Data has bad format.\n\"" + nodeBuilder.getLocalName() +
- "\" does not exist in yang schema.");
+ throw new RestconfDocumentedException(
+ "Data has bad format.\n\"" + nodeBuilder.getLocalName() +
+ "\" does not exist in yang schema.",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
QName currentAugment = null;
else {
currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint);
if (nodeBuilder.getQname() == null) {
- throw new ResponseException(Status.BAD_REQUEST,
+ throw new RestconfDocumentedException(
"Data has bad format.\nIf data is in XML format then namespace for \"" +
nodeBuilder.getLocalName() +
"\" should be \"" + schema.getQName().getNamespace() + "\".\n" +
"If data is in JSON format then module name for \"" + nodeBuilder.getLocalName() +
"\" should be corresponding to namespace \"" +
- schema.getQName().getNamespace() + "\".");
+ schema.getQName().getNamespace() + "\".",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
.append("\n");
}
- throw new ResponseException(Status.BAD_REQUEST,
+ throw new RestconfDocumentedException(
"Node \"" + child.getLocalName() +
"\" is added as augment from more than one module. " +
"Therefore node must have namespace (XML format) or module name (JSON format)." +
- "\nThe node is added as augment from modules with namespaces:\n" + builder);
+ "\nThe node is added as augment from modules with namespaces:\n" + builder,
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
boolean rightNodeSchemaFound = false;
}
if (!rightNodeSchemaFound) {
- throw new ResponseException(Status.BAD_REQUEST,
- "Schema node \"" + child.getLocalName() + "\" was not found in module.");
+ throw new RestconfDocumentedException(
+ "Schema node \"" + child.getLocalName() + "\" was not found in module.",
+ ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT );
}
}
}
if (!foundKey) {
- throw new ResponseException(Status.BAD_REQUEST,
+ throw new RestconfDocumentedException(
"Missing key in URI \"" + listKey.getLocalName() +
- "\" of list \"" + schema.getQName().getLocalName() + "\"");
+ "\" of list \"" + schema.getQName().getLocalName() + "\"",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
}
}
}
*/
package org.opendaylight.controller.sal.restconf.rpc.impl;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
public abstract class AbstractRpcExecutor implements RpcExecutor {
public RpcDefinition getRpcDefinition() {
return rpcDef;
}
+
+ protected RpcResult<CompositeNode> getRpcResult(
+ Future<RpcResult<CompositeNode>> fromFuture ) {
+ try {
+ return fromFuture.get();
+ }
+ catch( InterruptedException e ) {
+ throw new RestconfDocumentedException(
+ "The operation was interrupted while executing and did not complete.",
+ ErrorType.RPC, ErrorTag.PARTIAL_OPERATION );
+ }
+ catch( ExecutionException e ) {
+ Throwable cause = e.getCause();
+ if( cause instanceof CancellationException ) {
+ throw new RestconfDocumentedException(
+ "The operation was cancelled while executing.",
+ ErrorType.RPC, ErrorTag.PARTIAL_OPERATION );
+ }
+ else if( cause != null ){
+ while( cause.getCause() != null ) {
+ cause = cause.getCause();
+ }
+
+ if( cause instanceof IllegalArgumentException ) {
+ throw new RestconfDocumentedException(
+ cause.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
+ }
+
+ throw new RestconfDocumentedException(
+ "The operation encountered an unexpected error while executing.", cause );
+ }
+ else {
+ throw new RestconfDocumentedException(
+ "The operation encountered an unexpected error while executing.", e );
+ }
+ }
+ }
}
\ No newline at end of file
@Override
public RpcResult<CompositeNode> invokeRpc(CompositeNode rpcRequest) {
- return broker.invokeRpc( getRpcDefinition().getQName(), rpcRequest );
+ return getRpcResult( broker.invokeRpc( getRpcDefinition().getQName(), rpcRequest ) );
}
}
\ No newline at end of file
*/
package org.opendaylight.controller.sal.restconf.rpc.impl;
-import java.util.concurrent.ExecutionException;
-
-import javax.ws.rs.core.Response.Status;
-
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.ListenableFuture;
/**
* Provides an implementation which invokes rpc methods via a mounted yang data model.
}
@Override
- public RpcResult<CompositeNode> invokeRpc( CompositeNode rpcRequest ) throws ResponseException {
- ListenableFuture<RpcResult<CompositeNode>> rpcFuture =
- mountPoint.rpc( getRpcDefinition().getQName(), rpcRequest);
- try {
- return rpcFuture.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new ResponseException(Status.INTERNAL_SERVER_ERROR,
- e.getCause().getMessage() );
- }
+ public RpcResult<CompositeNode> invokeRpc( CompositeNode rpcRequest )
+ throws RestconfDocumentedException {
+ return getRpcResult( mountPoint.rpc( getRpcDefinition().getQName(), rpcRequest ) );
}
}
\ No newline at end of file
package org.opendaylight.controller.sal.restconf.impl.json.to.cnsn.test;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Set;
-import javax.ws.rs.WebApplicationException;
-
import org.junit.Ignore;
import org.junit.Test;
import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.controller.sal.restconf.impl.test.TestUtils;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.JsonSyntaxException;
-
public class JsonToCnSnTest {
private static final Logger LOG = LoggerFactory.getLogger(JsonToCnSnTest.class);
@Test
public void incorrectTopLevelElementsTest() {
- Throwable cause1 = null;
+ RestconfDocumentedException cause1 = null;
try {
- TestUtils
- .readInputToCnSn("/json-to-cnsn/wrong-top-level1.json", true, JsonToCompositeNodeProvider.INSTANCE);
- } catch (WebApplicationException e) {
+ TestUtils.readInputToCnSn("/json-to-cnsn/wrong-top-level1.json", true, JsonToCompositeNodeProvider.INSTANCE);
+ } catch (RestconfDocumentedException e) {
cause1 = e;
}
assertNotNull(cause1);
- assertTrue(cause1
- .getCause()
- .getMessage()
- .contains(
- "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."));
+ assertTrue(cause1.getErrors().get( 0 ).getErrorMessage().contains(
+ "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."));
- Throwable cause2 = null;
+ RestconfDocumentedException cause2 = null;
try {
TestUtils
- .readInputToCnSn("/json-to-cnsn/wrong-top-level2.json", true, JsonToCompositeNodeProvider.INSTANCE);
- } catch (WebApplicationException e) {
+ .readInputToCnSn("/json-to-cnsn/wrong-top-level2.json", true, JsonToCompositeNodeProvider.INSTANCE);
+ } catch (RestconfDocumentedException e) {
cause2 = e;
}
assertNotNull(cause2);
- assertTrue(cause2.getCause().getMessage().contains("Json Object should contain one element"));
+ assertTrue(cause2.getErrors().get( 0 ).getErrorMessage().contains(
+ "Json Object should contain one element"));
- Throwable cause3 = null;
+ RestconfDocumentedException cause3 = null;
try {
TestUtils
- .readInputToCnSn("/json-to-cnsn/wrong-top-level3.json", true, JsonToCompositeNodeProvider.INSTANCE);
- } catch (WebApplicationException e) {
+
+ .readInputToCnSn("/json-to-cnsn/wrong-top-level3.json", true, JsonToCompositeNodeProvider.INSTANCE);
+ } catch (RestconfDocumentedException e) {
cause3 = e;
}
assertNotNull(cause3);
- assertTrue(cause3
- .getCause()
- .getMessage()
- .contains(
- "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."));
+ assertTrue(cause3.getErrors().get( 0 ).getErrorMessage().contains(
+ "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."));
}
String reason = null;
try {
TestUtils.readInputToCnSn("/json-to-cnsn/empty-data1.json", true, JsonToCompositeNodeProvider.INSTANCE);
- } catch (JsonSyntaxException e) {
- reason = e.getMessage();
+ } catch (RestconfDocumentedException e) {
+ reason = e.getErrors().get( 0 ).getErrorMessage();
}
assertTrue(reason.contains("Expected value at line"));
@Ignore
@Test
public void loadDataAugmentedSchemaMoreEqualNamesTest() {
- boolean exceptionCaught = false;
- try {
- loadAndNormalizeData("/common/augment/json/dataa.json", "/common/augment/yang", "cont", "main");
- loadAndNormalizeData("/common/augment/json/datab.json", "/common/augment/yang", "cont", "main");
- } catch (ResponseException e) {
- exceptionCaught = true;
- }
+ loadAndNormalizeData("/common/augment/json/dataa.json", "/common/augment/yang", "cont", "main");
+ loadAndNormalizeData("/common/augment/json/datab.json", "/common/augment/yang", "cont", "main");
- assertFalse(exceptionCaught);
}
private void simpleTest(final String jsonPath, final String yangPath, final String topLevelElementName, final String namespace,
try {
TestUtils.readInputToCnSn("/json-to-cnsn/unsupported-json-format.json", true,
JsonToCompositeNodeProvider.INSTANCE);
- } catch (WebApplicationException e) {
- exceptionMessage = e.getCause().getMessage();
+ } catch (RestconfDocumentedException e) {
+ exceptionMessage = e.getErrors().get( 0 ).getErrorMessage();
}
assertTrue(exceptionMessage.contains("Root element of Json has to be Object"));
}
package org.opendaylight.controller.sal.restconf.impl.test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import java.util.Map;
import java.util.concurrent.Future;
-import javax.ws.rs.core.Response.Status;
-
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError;
import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.controller.sal.streams.listeners.Notificator;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
assertSame( "readOperationalDataBehindMountPoint", dataNode, actualNode );
}
- @Test(expected=ResponseException.class)
+ @Test(expected=RestconfDocumentedException.class)
public void testReadOperationalDataWithNoDataBroker() {
brokerFacade.setDataService( null );
@SuppressWarnings("unchecked")
@Test
- public void testInvokeRpc() {
+ public void testInvokeRpc() throws Exception {
RpcResult<CompositeNode> expResult = mock( RpcResult.class );
Future<RpcResult<CompositeNode>> future = Futures.immediateFuture( expResult );
when( mockConsumerSession.rpc( qname, dataNode ) ).thenReturn( future );
- RpcResult<CompositeNode> actualResult = brokerFacade.invokeRpc( qname, dataNode );
+ Future<RpcResult<CompositeNode>> actualFuture = brokerFacade.invokeRpc( qname, dataNode );
+ assertNotNull( "Future is null", actualFuture );
+ RpcResult<CompositeNode> actualResult = actualFuture.get();
assertSame( "invokeRpc", expResult, actualResult );
}
- @Test(expected=ResponseException.class)
- public void testInvokeRpcWithException() {
- Exception mockEx = new Exception( "mock" );
- Future<RpcResult<CompositeNode>> future = Futures.immediateFailedFuture( mockEx );
- when( mockConsumerSession.rpc( qname, dataNode ) ).thenReturn( future );
-
- brokerFacade.invokeRpc( qname, dataNode );
- }
-
- @Test(expected=ResponseException.class)
+ @Test(expected=RestconfDocumentedException.class)
public void testInvokeRpcWithNoConsumerSession() {
brokerFacade.setContext( null );
inOrder.verify( mockTransaction ).commit();
}
- @Test(expected=ResponseException.class)
+ @Test(expected=RestconfDocumentedException.class)
public void testCommitConfigurationDataPostAlreadyExists() {
when( dataBroker.beginTransaction() ).thenReturn( mockTransaction );
mockTransaction.putConfigurationData( instanceID, dataNode );
.thenReturn( dataNode );
try {
brokerFacade.commitConfigurationDataPost( instanceID, dataNode );
- } catch (ResponseException e) {
- assertEquals("Unexpect Exception Status -> "
- + "http://tools.ietf.org/html/draft-bierman-netconf-restconf-03#page-48",
- (e.getResponse().getStatus()), Status.CONFLICT.getStatusCode());
+ }
+ catch (RestconfDocumentedException e) {
+ assertEquals("getErrorTag",
+ RestconfError.ErrorTag.DATA_EXISTS, e.getErrors().get( 0 ).getErrorTag());
throw e;
}
}
inOrder.verify( mockTransaction ).commit();
}
- @Test(expected=ResponseException.class)
+ @Test(expected=RestconfDocumentedException.class)
public void testCommitConfigurationDataPostBehindMountPointAlreadyExists() {
when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction );
try {
brokerFacade.commitConfigurationDataPostBehindMountPoint( mockMountInstance,
instanceID, dataNode );
- } catch (ResponseException e) {
- assertEquals("Unexpect Exception Status -> "
- + "http://tools.ietf.org/html/draft-bierman-netconf-restconf-03#page-48",
- e.getResponse().getStatus(), Status.CONFLICT.getStatusCode());
+ }
+ catch (RestconfDocumentedException e) {
+ assertEquals("getErrorTag",
+ RestconfError.ErrorTag.DATA_EXISTS, e.getErrors().get( 0 ).getErrorTag());
throw e;
}
}
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
+import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
resourceConfig = resourceConfig.registerInstances(restConf, StructuredDataToXmlProvider.INSTANCE,
StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
JsonToCompositeNodeProvider.INSTANCE);
+ resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
return resourceConfig;
}
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
-import javax.ws.rs.core.Response.Status;
-
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
+import org.opendaylight.controller.sal.common.util.RpcErrors;
+import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
import org.opendaylight.controller.sal.restconf.impl.StructuredData;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.data.api.ModifyAction;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
public class InvokeRpcMethodTest {
private RestconfImpl restconfImpl = null;
private static ControllerContext controllerContext = null;
- private class AnswerImpl implements Answer<RpcResult<CompositeNode>> {
- @Override
- public RpcResult<CompositeNode> answer(final InvocationOnMock invocation) throws Throwable {
- CompositeNode compNode = (CompositeNode) invocation.getArguments()[1];
- return new DummyRpcResult.Builder<CompositeNode>().result(compNode).isSuccessful(true).build();
- }
- }
@BeforeClass
public static void init() throws FileNotFoundException {
restconf.setBroker(mockedBrokerFacade);
restconf.setControllerContext(contContext);
- when(mockedBrokerFacade.invokeRpc(any(QName.class), any(CompositeNode.class))).thenAnswer(new AnswerImpl());
+ CompositeNode payload = preparePayload();
+
+ when(mockedBrokerFacade.invokeRpc(any(QName.class), any(CompositeNode.class)))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture(
+ Rpcs.<CompositeNode>getRpcResult( true ) ) );
- StructuredData structData = restconf.invokeRpc("invoke-rpc-module:rpc-test", preparePayload());
+ StructuredData structData = restconf.invokeRpc("invoke-rpc-module:rpc-test", payload);
assertTrue(structData == null);
}
@Test
public void testInvokeRpcWithNoPayloadRpc_FailNoErrors() {
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(false);
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( false );
- ArgumentCaptor<CompositeNode> payload = ArgumentCaptor
- .forClass(CompositeNode.class);
BrokerFacade brokerFacade = mock(BrokerFacade.class);
- when(
- brokerFacade.invokeRpc(
- eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
- payload.capture())).thenReturn(rpcResult);
+ when( brokerFacade.invokeRpc(
+ eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
+ any(CompositeNode.class)))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ) );
restconfImpl.setBroker(brokerFacade);
try {
restconfImpl.invokeRpc("toaster:cancel-toast", "");
fail("Expected an exception to be thrown.");
- } catch (ResponseException e) {
- assertEquals(e.getMessage(),
- Status.INTERNAL_SERVER_ERROR.getStatusCode(), e
- .getResponse().getStatus());
+ }
+ catch (RestconfDocumentedException e) {
+ verifyRestconfDocumentedException( e, 0, ErrorType.RPC, ErrorTag.OPERATION_FAILED,
+ Optional.<String>absent(), Optional.<String>absent() );
}
}
- @Test
- public void testInvokeRpcWithNoPayloadRpc_FailWithRpcError() {
- List<RpcError> rpcErrors = new LinkedList<RpcError>();
+ void verifyRestconfDocumentedException( final RestconfDocumentedException e, final int index,
+ final ErrorType expErrorType, final ErrorTag expErrorTag,
+ final Optional<String> expErrorMsg,
+ final Optional<String> expAppTag ) {
+ RestconfError actual = null;
+ try {
+ actual = e.getErrors().get( index );
+ }
+ catch( ArrayIndexOutOfBoundsException ex ) {
+ fail( "RestconfError not found at index " + index );
+ }
+
+ assertEquals( "getErrorType", expErrorType, actual.getErrorType() );
+ assertEquals( "getErrorTag", expErrorTag, actual.getErrorTag() );
+ assertNotNull( "getErrorMessage is null", actual.getErrorMessage() );
+
+ if( expErrorMsg.isPresent() ) {
+ assertEquals( "getErrorMessage", expErrorMsg.get(), actual.getErrorMessage() );
+ }
- RpcError unknownError = mock(RpcError.class);
- when( unknownError.getTag() ).thenReturn( "bogusTag" );
- rpcErrors.add( unknownError );
+ if( expAppTag.isPresent() ) {
+ assertEquals( "getErrorAppTag", expAppTag.get(), actual.getErrorAppTag() );
+ }
+ }
- RpcError knownError = mock( RpcError.class );
- when( knownError.getTag() ).thenReturn( "in-use" );
- rpcErrors.add( knownError );
+ @Test
+ public void testInvokeRpcWithNoPayloadRpc_FailWithRpcError() {
+ List<RpcError> rpcErrors = Arrays.asList(
+ RpcErrors.getRpcError( null, "bogusTag", null, ErrorSeverity.ERROR, "foo",
+ RpcError.ErrorType.TRANSPORT, null ),
+ RpcErrors.getRpcError( "app-tag", "in-use", null, ErrorSeverity.WARNING, "bar",
+ RpcError.ErrorType.RPC, null ));
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(false);
- when(rpcResult.getErrors()).thenReturn( rpcErrors );
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( false, rpcErrors );
- ArgumentCaptor<CompositeNode> payload = ArgumentCaptor
- .forClass(CompositeNode.class);
BrokerFacade brokerFacade = mock(BrokerFacade.class);
- when(
- brokerFacade.invokeRpc(
- eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
- payload.capture())).thenReturn(rpcResult);
+ when( brokerFacade.invokeRpc(
+ eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
+ any(CompositeNode.class)))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ) );
restconfImpl.setBroker(brokerFacade);
try {
restconfImpl.invokeRpc("toaster:cancel-toast", "");
fail("Expected an exception to be thrown.");
- } catch (ResponseException e) {
- //TODO: Change to a 409 in the future - waiting on additional BUG to enhance this.
- assertEquals(e.getMessage(), 500, e.getResponse().getStatus());
+ }
+ catch (RestconfDocumentedException e) {
+ verifyRestconfDocumentedException( e, 0, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED,
+ Optional.of( "foo" ), Optional.<String>absent() );
+ verifyRestconfDocumentedException( e, 1, ErrorType.RPC, ErrorTag.IN_USE,
+ Optional.of( "bar" ), Optional.of( "app-tag" ) );
}
}
@Test
public void testInvokeRpcWithNoPayload_Success() {
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(true);
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( true );
BrokerFacade brokerFacade = mock(BrokerFacade.class);
- when(
- brokerFacade.invokeRpc(
- eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
- any( CompositeNode.class ))).thenReturn(rpcResult);
+ when( brokerFacade.invokeRpc(
+ eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast")),
+ any( CompositeNode.class )))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ) );
restconfImpl.setBroker(brokerFacade);
try {
restconfImpl.invokeRpc("toaster:cancel-toast", " a payload ");
fail("Expected an exception");
- } catch (ResponseException e) {
- assertEquals(e.getMessage(),
- Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), e
- .getResponse().getStatus());
+ } catch (RestconfDocumentedException e) {
+ verifyRestconfDocumentedException( e, 0, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ Optional.<String>absent(), Optional.<String>absent() );
}
}
try {
restconfImpl.invokeRpc("toaster:bad-method", "");
fail("Expected an exception");
- } catch (ResponseException e) {
- assertEquals(e.getMessage(), Status.NOT_FOUND.getStatusCode(), e
- .getResponse().getStatus());
+ }
+ catch (RestconfDocumentedException e) {
+ verifyRestconfDocumentedException( e, 0, ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT,
+ Optional.<String>absent(), Optional.<String>absent() );
}
}
@Test
public void testInvokeRpcMethodWithInput() {
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(true);
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( true );
CompositeNode payload = mock(CompositeNode.class);
BrokerFacade brokerFacade = mock(BrokerFacade.class);
- when(
- brokerFacade.invokeRpc(
- eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)make-toast")),
- any(CompositeNode.class))).thenReturn(rpcResult);
+ when( brokerFacade.invokeRpc(
+ eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)make-toast")),
+ any(CompositeNode.class)))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ) );
restconfImpl.setBroker(brokerFacade);
try {
restconfImpl.invokeRpc("toaster/slash", "");
fail("Expected an exception.");
- } catch (ResponseException e) {
- assertEquals(e.getMessage(), Status.NOT_FOUND.getStatusCode(), e
- .getResponse().getStatus());
+ }
+ catch (RestconfDocumentedException e) {
+ verifyRestconfDocumentedException( e, 0, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+ Optional.<String>absent(), Optional.<String>absent() );
}
}
@Test
public void testInvokeRpcWithNoPayloadWithOutput_Success() {
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(true);
-
CompositeNode compositeNode = mock( CompositeNode.class );
- when( rpcResult.getResult() ).thenReturn( compositeNode );
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( true, compositeNode,
+ Collections.<RpcError>emptyList() );
BrokerFacade brokerFacade = mock(BrokerFacade.class);
when( brokerFacade.invokeRpc(
- eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)testOutput")),
- any( CompositeNode.class ))).thenReturn(rpcResult);
+ eq(QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)testOutput")),
+ any( CompositeNode.class )))
+ .thenReturn( Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ) );
restconfImpl.setBroker(brokerFacade);
- StructuredData output = restconfImpl.invokeRpc("toaster:testOutput",
- "");
+ StructuredData output = restconfImpl.invokeRpc("toaster:testOutput", "");
assertNotNull( output );
assertSame( compositeNode, output.getData() );
assertNotNull( output.getSchema() );
@Test
public void testMountedRpcCallNoPayload_Success() throws Exception
{
- RpcResult<CompositeNode> rpcResult = mock(RpcResult.class);
- when(rpcResult.isSuccessful()).thenReturn(true);
+ RpcResult<CompositeNode> rpcResult = Rpcs.<CompositeNode>getRpcResult( true );
ListenableFuture<RpcResult<CompositeNode>> mockListener = mock( ListenableFuture.class );
when( mockListener.get() ).thenReturn( rpcResult );
//additional validation in the fact that the restconfImpl does not throw an exception.
}
-
-
}
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
dataLoad("/normalize-node/yang/");
}
- @Test
+ @Test(expected=RestconfDocumentedException.class)
public void namespaceNotNullAndInvalidNamespaceAndNoModuleNameTest() {
- boolean exceptionReised = false;
- try {
- TestUtils.normalizeCompositeNode(prepareCnSn("wrongnamespace"), modules, schemaNodePath);
- } catch (ResponseException e) {
- exceptionReised = true;
- }
- assertTrue(exceptionReised);
+
+ TestUtils.normalizeCompositeNode(prepareCnSn("wrongnamespace"), modules, schemaNodePath);
}
@Test
public void namespaceNullTest() {
- String exceptionMessage = null;
- try {
- TestUtils.normalizeCompositeNode(prepareCnSn(null), modules, schemaNodePath);
- } catch (ResponseException e) {
- exceptionMessage = String.valueOf(e.getResponse().getEntity());
- }
- assertNull(exceptionMessage);
+
+ TestUtils.normalizeCompositeNode(prepareCnSn(null), modules, schemaNodePath);
}
@Test
public void namespaceValidNamespaceTest() {
- String exceptionMessage = null;
- try {
- TestUtils.normalizeCompositeNode(prepareCnSn("normalize:node:module"), modules, schemaNodePath);
- } catch (ResponseException e) {
- exceptionMessage = String.valueOf(e.getResponse().getEntity());
- }
- assertNull(exceptionMessage);
+
+ TestUtils.normalizeCompositeNode(prepareCnSn("normalize:node:module"), modules, schemaNodePath);
}
@Test
public void namespaceValidModuleNameTest() {
- String exceptionMessage = null;
- try {
- TestUtils.normalizeCompositeNode(prepareCnSn("normalize-node-module"), modules, schemaNodePath);
- } catch (ResponseException e) {
- exceptionMessage = String.valueOf(e.getResponse().getEntity());
- }
- assertNull(exceptionMessage);
+
+ TestUtils.normalizeCompositeNode(prepareCnSn("normalize-node-module"), modules, schemaNodePath);
}
private CompositeNode prepareCnSn(String namespace) {
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.FileNotFoundException;
import org.junit.rules.ExpectedException;
import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
public class RestGetAugmentedElementWhenEqualNamesTest {
-
+
private static ControllerContext controllerContext = ControllerContext.getInstance();
-
+
@Rule
public ExpectedException exception = ExpectedException.none();
-
+
@BeforeClass
public static void init() throws FileNotFoundException {
SchemaContext schemaContextTestModule = TestUtils.loadSchemaContext("/common/augment/yang");
iiWithData = controllerContext.toInstanceIdentifier("main:cont/augment-main-b:cont1");
assertEquals("ns:augment:main:b", iiWithData.getSchemaNode().getQName().getNamespace().toString());
}
-
+
@Test
public void nodeWithoutNamespaceHasMoreAugments() {
- boolean exceptionCaught = false;
try {
controllerContext.toInstanceIdentifier("main:cont/cont1");
- } catch (ResponseException e) {
- assertTrue(((String) e.getResponse().getEntity()).contains("is added as augment from more than one module"));
- exceptionCaught = true;
+ fail( "Expected exception" );
+ } catch (RestconfDocumentedException e) {
+ assertTrue(e.getErrors().get( 0 ).getErrorMessage().contains(
+ "is added as augment from more than one module"));
}
- assertTrue(exceptionCaught);
}
-
}
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.opendaylight.controller.sal.core.api.mount.MountInstance;
import org.opendaylight.controller.sal.core.api.mount.MountService;
import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
+import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
JsonToCompositeNodeProvider.INSTANCE);
+ resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
return resourceConfig;
}
/**
* MountPoint test. URI represents mount point.
- *
+ *
* Slashes in URI behind mount point. lst1 element with key
* GigabitEthernet0%2F0%2F0%2F0 (GigabitEthernet0/0/0/0) is requested via
* GET HTTP operation. It is tested whether %2F character is replaced with
* simple / in InstanceIdentifier parameter in method
* {@link BrokerFacade#readConfigurationDataBehindMountPoint(MountInstance, InstanceIdentifier)}
* which is called in method {@link RestconfImpl#readConfigurationData}
- *
- *
+ *
+ *
* @throws ParseException
*/
@Test
import org.opendaylight.controller.sal.core.api.mount.MountService;
import org.opendaylight.controller.sal.rest.api.Draft02;
import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
+import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import com.google.common.util.concurrent.Futures;
+
public class RestPostOperationTest extends JerseyTest {
private static String xmlDataAbsolutePath;
resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
JsonToCompositeNodeProvider.INSTANCE);
+ resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
return resourceConfig;
}
assertEquals(500, post(uri, MediaType.APPLICATION_XML, xmlDataRpcInput));
uri = "/operations/test-module:rpc-wrongtest";
- assertEquals(404, post(uri, MediaType.APPLICATION_XML, xmlDataRpcInput));
+ assertEquals(400, post(uri, MediaType.APPLICATION_XML, xmlDataRpcInput));
}
@Test
private void mockInvokeRpc(CompositeNode result, boolean sucessful) {
RpcResult<CompositeNode> rpcResult = new DummyRpcResult.Builder<CompositeNode>().result(result)
.isSuccessful(sucessful).build();
- when(brokerFacade.invokeRpc(any(QName.class), any(CompositeNode.class))).thenReturn(rpcResult);
+ when(brokerFacade.invokeRpc(any(QName.class), any(CompositeNode.class)))
+ .thenReturn(Futures.<RpcResult<CompositeNode>>immediateFuture( rpcResult ));
}
private void mockCommitConfigurationDataPostMethod(TransactionStatus statusName) {
--- /dev/null
+/*
+ * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.sal.rest.api.Draft02;
+import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
+import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
+import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError;
+import org.opendaylight.controller.sal.restconf.impl.StructuredData;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for RestconfDocumentedExceptionMapper.
+ *
+ * @author Thomas Pantelis
+ */
+public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
+
+ interface ErrorInfoVerifier {
+ void verifyXML( Node errorInfoNode );
+ void verifyJson( JsonElement errorInfoElement );
+ }
+
+ static class ComplexErrorInfoVerifier implements ErrorInfoVerifier {
+
+ Map<String, String> expErrorInfo;
+
+ public ComplexErrorInfoVerifier( Map<String, String> expErrorInfo ) {
+ this.expErrorInfo = expErrorInfo;
+ }
+
+ @Override
+ public void verifyXML( Node errorInfoNode ) {
+
+ Map<String, String> mutableExpMap = Maps.newHashMap( expErrorInfo );
+ NodeList childNodes = errorInfoNode.getChildNodes();
+ for( int i = 0; i < childNodes.getLength(); i++ ) {
+ Node child = childNodes.item( i );
+ if( child instanceof Element ) {
+ String expValue = mutableExpMap.remove( child.getNodeName() );
+ assertNotNull( "Found unexpected \"error-info\" child node: " +
+ child.getNodeName(), expValue );
+ assertEquals( "Text content for \"error-info\" child node " +
+ child.getNodeName(), expValue, child.getTextContent() );
+ }
+ }
+
+ if( !mutableExpMap.isEmpty() ) {
+ fail( "Missing \"error-info\" child nodes: " + mutableExpMap );
+ }
+ }
+
+ @Override
+ public void verifyJson( JsonElement errorInfoElement ) {
+
+ assertTrue( "\"error-info\" Json element is not an Object",
+ errorInfoElement.isJsonObject() );
+
+ Map<String, String> actualErrorInfo = Maps.newHashMap();
+ for( Entry<String, JsonElement> entry: errorInfoElement.getAsJsonObject().entrySet() ) {
+ String leafName = entry.getKey();
+ JsonElement leafElement = entry.getValue();
+ actualErrorInfo.put( leafName, leafElement.getAsString() );
+ }
+
+ Map<String, String> mutableExpMap = Maps.newHashMap( expErrorInfo );
+ for( Entry<String,String> actual: actualErrorInfo.entrySet() ) {
+ String expValue = mutableExpMap.remove( actual.getKey() );
+ assertNotNull( "Found unexpected \"error-info\" child node: " +
+ actual.getKey(), expValue );
+ assertEquals( "Text content for \"error-info\" child node " +
+ actual.getKey(), expValue, actual.getValue() );
+ }
+
+ if( !mutableExpMap.isEmpty() ) {
+ fail( "Missing \"error-info\" child nodes: " + mutableExpMap );
+ }
+ }
+ }
+
+ static class SimpleErrorInfoVerifier implements ErrorInfoVerifier {
+
+ String expTextContent;
+
+ public SimpleErrorInfoVerifier( String expErrorInfo ) {
+ this.expTextContent = expErrorInfo;
+ }
+
+ void verifyContent( String actualContent ) {
+ assertNotNull( "Actual \"error-info\" text content is null", actualContent );
+ assertTrue( "", actualContent.contains( expTextContent ) );
+ }
+
+ @Override
+ public void verifyXML( Node errorInfoNode ) {
+ verifyContent( errorInfoNode.getTextContent() );
+ }
+
+ @Override
+ public void verifyJson( JsonElement errorInfoElement ) {
+ verifyContent( errorInfoElement.getAsString() );
+ }
+ }
+
+ static RestconfService mockRestConf = mock( RestconfService.class );
+
+ static XPath XPATH = XPathFactory.newInstance().newXPath();
+ static XPathExpression ERROR_LIST;
+ static XPathExpression ERROR_TYPE;
+ static XPathExpression ERROR_TAG;
+ static XPathExpression ERROR_MESSAGE;
+ static XPathExpression ERROR_APP_TAG;
+ static XPathExpression ERROR_INFO;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ ControllerContext.getInstance().setGlobalSchema( TestUtils.loadSchemaContext("/modules") );
+
+ NamespaceContext nsContext = new NamespaceContext() {
+ @Override
+ public Iterator getPrefixes( String namespaceURI ) {
+ return null;
+ }
+
+ @Override
+ public String getPrefix( String namespaceURI ) {
+ return null;
+ }
+
+ @Override
+ public String getNamespaceURI( String prefix ) {
+ return "ietf-restconf".equals( prefix ) ? Draft02.RestConfModule.NAMESPACE : null;
+ }
+ };
+
+ XPATH.setNamespaceContext( nsContext );
+ ERROR_LIST = XPATH.compile( "ietf-restconf:errors/ietf-restconf:error" );
+ ERROR_TYPE = XPATH.compile( "ietf-restconf:error-type" );
+ ERROR_TAG = XPATH.compile( "ietf-restconf:error-tag" );
+ ERROR_MESSAGE = XPATH.compile( "ietf-restconf:error-message" );
+ ERROR_APP_TAG = XPATH.compile( "ietf-restconf:error-app-tag" );
+ ERROR_INFO = XPATH.compile( "ietf-restconf:error-info" );
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ reset( mockRestConf );
+ super.setUp();
+ }
+
+ @Override
+ protected Application configure() {
+ ResourceConfig resourceConfig = new ResourceConfig();
+ resourceConfig = resourceConfig.registerInstances( mockRestConf, StructuredDataToXmlProvider.INSTANCE,
+ StructuredDataToJsonProvider.INSTANCE );
+ resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
+ return resourceConfig;
+ }
+
+ void stageMockEx( RestconfDocumentedException ex ) {
+ reset( mockRestConf );
+ when( mockRestConf.readOperationalData( any( String.class ) ) ).thenThrow( ex );
+ }
+
+ void testJsonResponse( RestconfDocumentedException ex, Status expStatus, ErrorType expErrorType,
+ ErrorTag expErrorTag, String expErrorMessage, String expErrorAppTag,
+ ErrorInfoVerifier errorInfoVerifier ) throws Exception {
+
+ stageMockEx( ex );
+
+ Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get();
+
+ InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON, expStatus );
+
+ verifyJsonResponseBody( stream, expErrorType, expErrorTag, expErrorMessage,
+ expErrorAppTag, errorInfoVerifier );
+ }
+
+ @Test
+ public void testToJsonResponseWithMessageOnly() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error" ), Status.INTERNAL_SERVER_ERROR,
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null );
+
+ // To test verification code
+// String json =
+// "{ errors: {" +
+// " error: [{" +
+// " error-tag : \"operation-failed\"" +
+// " ,error-type : \"application\"" +
+// " ,error-message : \"An error occurred\"" +
+// " ,error-info : {" +
+// " session-id: \"123\"" +
+// " ,address: \"1.2.3.4\"" +
+// " }" +
+// " }]" +
+// " }" +
+// "}";
+//
+// verifyJsonResponseBody( new java.io.StringBufferInputStream(json ), ErrorType.APPLICATION,
+// ErrorTag.OPERATION_FAILED, "An error occurred", null,
+// com.google.common.collect.ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) );
+ }
+
+ @Test
+ public void testToJsonResponseWithInUseErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.IN_USE ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.IN_USE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithInvalidValueErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.RPC,
+ ErrorTag.INVALID_VALUE ),
+ Status.BAD_REQUEST, ErrorType.RPC,
+ ErrorTag.INVALID_VALUE, "mock error", null, null );
+
+ }
+
+ @Test
+ public void testToJsonResponseWithTooBigErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.TRANSPORT,
+ ErrorTag.TOO_BIG ),
+ Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT,
+ ErrorTag.TOO_BIG, "mock error", null, null );
+
+ }
+
+ @Test
+ public void testToJsonResponseWithMissingAttributeErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.MISSING_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithBadAttributeErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.BAD_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.BAD_ATTRIBUTE, "mock error", null, null );
+ }
+ @Test
+ public void testToJsonResponseWithUnknownAttributeErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithBadElementErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.BAD_ELEMENT ),
+ Status.BAD_REQUEST,
+ ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithUnknownElementErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithUnknownNamespaceErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_NAMESPACE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithMalformedMessageErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithAccessDeniedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.ACCESS_DENIED ),
+ Status.FORBIDDEN, ErrorType.PROTOCOL,
+ ErrorTag.ACCESS_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithLockDeniedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.LOCK_DENIED ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.LOCK_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithResourceDeniedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.RESOURCE_DENIED ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.RESOURCE_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithRollbackFailedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.ROLLBACK_FAILED ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.ROLLBACK_FAILED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithDataExistsErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithDataMissingErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithOperationNotSupportedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED ),
+ Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithOperationFailedErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_FAILED ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_FAILED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithPartialOperationErrorTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.PARTIAL_OPERATION ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.PARTIAL_OPERATION, "mock error", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithErrorAppTag() throws Exception {
+
+ testJsonResponse( new RestconfDocumentedException( new RestconfError(
+ ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
+ "mock error", "mock-app-tag" ) ),
+ Status.BAD_REQUEST, ErrorType.APPLICATION,
+ ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null );
+ }
+
+ @Test
+ public void testToJsonResponseWithMultipleErrors() throws Exception {
+
+ List<RestconfError> errorList = Arrays.asList(
+ new RestconfError( ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1" ),
+ new RestconfError( ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2" ) );
+ stageMockEx( new RestconfDocumentedException( errorList ) );
+
+ Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get();
+
+ InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON, Status.CONFLICT );
+
+ JsonArray arrayElement = parseJsonErrorArrayElement( stream );
+
+ assertEquals( "\"error\" Json array element length", 2, arrayElement.size() );
+
+ verifyJsonErrorNode( arrayElement.get( 0 ), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED,
+ "mock error1", null, null );
+
+ verifyJsonErrorNode( arrayElement.get( 1 ), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED,
+ "mock error2", null, null );
+ }
+
+ @Test
+ public void testToJsonResponseWithErrorInfo() throws Exception {
+
+ String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
+ testJsonResponse( new RestconfDocumentedException( new RestconfError(
+ ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
+ "mock error", "mock-app-tag", errorInfo ) ),
+ Status.BAD_REQUEST, ErrorType.APPLICATION,
+ ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
+ new ComplexErrorInfoVerifier( ImmutableMap.of(
+ "session-id", "123", "address", "1.2.3.4" ) ) );
+ }
+
+ @Test
+ public void testToJsonResponseWithExceptionCause() throws Exception {
+
+ Exception cause = new Exception( "mock exception cause" );
+ testJsonResponse( new RestconfDocumentedException( "mock error", cause ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION,
+ ErrorTag.OPERATION_FAILED, "mock error", null,
+ new SimpleErrorInfoVerifier( cause.getMessage() ) );
+ }
+
+ void testXMLResponse( RestconfDocumentedException ex, Status expStatus, ErrorType expErrorType,
+ ErrorTag expErrorTag, String expErrorMessage,
+ String expErrorAppTag, ErrorInfoVerifier errorInfoVerifier ) throws Exception
+ {
+ stageMockEx( ex );
+
+ Response resp = target("/operational/foo").request( MediaType.APPLICATION_XML ).get();
+
+ InputStream stream = verifyResponse( resp, MediaType.APPLICATION_XML, expStatus );
+
+ verifyXMLResponseBody( stream, expErrorType, expErrorTag, expErrorMessage,
+ expErrorAppTag, errorInfoVerifier );
+ }
+
+ @Test
+ public void testToXMLResponseWithMessageOnly() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error" ), Status.INTERNAL_SERVER_ERROR,
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null );
+
+ // To test verification code
+// String xml =
+// "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">"+
+// " <error>" +
+// " <error-type>application</error-type>"+
+// " <error-tag>operation-failed</error-tag>"+
+// " <error-message>An error occurred</error-message>"+
+// " <error-info>" +
+// " <session-id>123</session-id>" +
+// " <address>1.2.3.4</address>" +
+// " </error-info>" +
+// " </error>" +
+// "</errors>";
+//
+// verifyXMLResponseBody( new java.io.StringBufferInputStream(xml), ErrorType.APPLICATION,
+// ErrorTag.OPERATION_FAILED, "An error occurred", null,
+// com.google.common.collect.ImmutableMap.of( "session-id", "123", "address", "1.2.3.4" ) );
+ }
+
+ @Test
+ public void testToXMLResponseWithInUseErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.IN_USE ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.IN_USE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithInvalidValueErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.RPC,
+ ErrorTag.INVALID_VALUE ),
+ Status.BAD_REQUEST, ErrorType.RPC,
+ ErrorTag.INVALID_VALUE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithTooBigErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.TRANSPORT,
+ ErrorTag.TOO_BIG ),
+ Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT,
+ ErrorTag.TOO_BIG, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithMissingAttributeErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.MISSING_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithBadAttributeErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.BAD_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.BAD_ATTRIBUTE, "mock error", null, null );
+ }
+ @Test
+ public void testToXMLResponseWithUnknownAttributeErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ATTRIBUTE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithBadElementErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.BAD_ELEMENT ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.BAD_ELEMENT, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithUnknownElementErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithUnknownNamespaceErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_NAMESPACE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithMalformedMessageErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE ),
+ Status.BAD_REQUEST, ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithAccessDeniedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.ACCESS_DENIED ),
+ Status.FORBIDDEN, ErrorType.PROTOCOL,
+ ErrorTag.ACCESS_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithLockDeniedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.LOCK_DENIED ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.LOCK_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithResourceDeniedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.RESOURCE_DENIED ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.RESOURCE_DENIED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithRollbackFailedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.ROLLBACK_FAILED ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.ROLLBACK_FAILED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithDataExistsErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.DATA_EXISTS, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithDataMissingErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING ),
+ Status.CONFLICT, ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithOperationNotSupportedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED ),
+ Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithOperationFailedErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_FAILED ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_FAILED, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithPartialOperationErrorTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( "mock error", ErrorType.PROTOCOL,
+ ErrorTag.PARTIAL_OPERATION ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL,
+ ErrorTag.PARTIAL_OPERATION, "mock error", null, null );
+ }
+
+ @Test
+ public void testToXMLResponseWithErrorAppTag() throws Exception {
+
+ testXMLResponse( new RestconfDocumentedException( new RestconfError(
+ ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
+ "mock error", "mock-app-tag" ) ),
+ Status.BAD_REQUEST, ErrorType.APPLICATION,
+ ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null );
+ }
+
+ @Test
+ public void testToXMLResponseWithErrorInfo() throws Exception {
+
+ String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
+ testXMLResponse( new RestconfDocumentedException( new RestconfError(
+ ErrorType.APPLICATION, ErrorTag.INVALID_VALUE,
+ "mock error", "mock-app-tag", errorInfo ) ),
+ Status.BAD_REQUEST, ErrorType.APPLICATION,
+ ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
+ new ComplexErrorInfoVerifier( ImmutableMap.of(
+ "session-id", "123", "address", "1.2.3.4" ) ) );
+ }
+
+ @Test
+ public void testToXMLResponseWithExceptionCause() throws Exception {
+
+ Exception cause = new Exception( "mock exception cause" );
+ testXMLResponse( new RestconfDocumentedException( "mock error", cause ),
+ Status.INTERNAL_SERVER_ERROR, ErrorType.APPLICATION,
+ ErrorTag.OPERATION_FAILED, "mock error", null,
+ new SimpleErrorInfoVerifier( cause.getMessage() ) );
+ }
+
+ @Test
+ public void testToXMLResponseWithMultipleErrors() throws Exception {
+
+ List<RestconfError> errorList = Arrays.asList(
+ new RestconfError( ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1" ),
+ new RestconfError( ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2" ) );
+ stageMockEx( new RestconfDocumentedException( errorList ) );
+
+ Response resp = target("/operational/foo").request( MediaType.APPLICATION_XML ).get();
+
+ InputStream stream = verifyResponse( resp, MediaType.APPLICATION_XML, Status.CONFLICT );
+
+ Document doc = parseXMLDocument( stream );
+
+ NodeList children = getXMLErrorList( doc, 2 );
+
+ verifyXMLErrorNode( children.item( 0 ), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED,
+ "mock error1", null, null );
+
+ verifyXMLErrorNode( children.item( 1 ), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED,
+ "mock error2", null, null );
+ }
+
+ @Test
+ public void testToResponseWithAcceptHeader() throws Exception {
+
+ stageMockEx( new RestconfDocumentedException( "mock error" ) );
+
+ Response resp = target("/operational/foo")
+ .request().header( "Accept", MediaType.APPLICATION_JSON ).get();
+
+ InputStream stream = verifyResponse( resp, MediaType.APPLICATION_JSON,
+ Status.INTERNAL_SERVER_ERROR );
+
+ verifyJsonResponseBody( stream, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error",
+ null, null );
+ }
+
+ @Test
+ public void testToResponseWithStatusOnly() throws Exception {
+
+ // The StructuredDataToJsonProvider should throw a RestconfDocumentedException with no data
+
+ when( mockRestConf.readOperationalData( any( String.class ) ) )
+ .thenReturn( new StructuredData( null, null, null ) );
+
+ Response resp = target("/operational/foo").request( MediaType.APPLICATION_JSON ).get();
+
+ verifyResponse( resp, MediaType.TEXT_PLAIN, Status.NOT_FOUND );
+ }
+
+ InputStream verifyResponse( Response resp, String expMediaType, Status expStatus ) {
+ assertEquals( "getMediaType", MediaType.valueOf( expMediaType ), resp.getMediaType() );
+ assertEquals( "getStatus", expStatus.getStatusCode(), resp.getStatus() );
+
+ Object entity = resp.getEntity();
+ assertEquals( "Response entity", true, entity instanceof InputStream );
+ InputStream stream = (InputStream)entity;
+ return stream;
+ }
+
+ void verifyJsonResponseBody( InputStream stream, ErrorType expErrorType, ErrorTag expErrorTag,
+ String expErrorMessage, String expErrorAppTag,
+ ErrorInfoVerifier errorInfoVerifier ) throws Exception {
+
+ JsonArray arrayElement = parseJsonErrorArrayElement( stream );
+
+ assertEquals( "\"error\" Json array element length", 1, arrayElement.size() );
+
+ verifyJsonErrorNode( arrayElement.get( 0 ), expErrorType, expErrorTag, expErrorMessage,
+ expErrorAppTag, errorInfoVerifier );
+ }
+
+ private JsonArray parseJsonErrorArrayElement( InputStream stream ) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteStreams.copy( stream, bos );
+
+ System.out.println("JSON: "+bos.toString());
+
+ JsonParser parser = new JsonParser();
+ JsonElement rootElement;
+
+ try {
+ rootElement = parser.parse(
+ new InputStreamReader( new ByteArrayInputStream( bos.toByteArray() ) ) );
+ }
+ catch( Exception e ) {
+ throw new IllegalArgumentException( "Invalid JSON response:\n" + bos.toString(), e );
+ }
+
+ assertTrue( "Root element of Json is not an Object", rootElement.isJsonObject() );
+
+ Set<Entry<String, JsonElement>> errorsEntrySet = rootElement.getAsJsonObject().entrySet();
+ assertEquals( "Json Object element set count", 1, errorsEntrySet.size() );
+
+ Entry<String, JsonElement> errorsEntry = errorsEntrySet.iterator().next();
+ JsonElement errorsElement = errorsEntry.getValue();
+ assertEquals( "First Json element name", "errors", errorsEntry.getKey() );
+ assertTrue( "\"errors\" Json element is not an Object", errorsElement.isJsonObject() );
+
+ Set<Entry<String, JsonElement>> errorListEntrySet = errorsElement.getAsJsonObject().entrySet();
+ assertEquals( "Root \"errors\" element child count", 1, errorListEntrySet.size() );
+
+ JsonElement errorListElement = errorListEntrySet.iterator().next().getValue();
+ assertEquals( "\"errors\" child Json element name", "error",
+ errorListEntrySet.iterator().next().getKey() );
+ assertTrue( "\"error\" Json element is not an Array", errorListElement.isJsonArray() );
+
+ return errorListElement.getAsJsonArray();
+ }
+
+ void verifyJsonErrorNode( JsonElement errorEntryElement, ErrorType expErrorType, ErrorTag expErrorTag,
+ String expErrorMessage, String expErrorAppTag,
+ ErrorInfoVerifier errorInfoVerifier ) {
+
+ JsonElement errorInfoElement = null;
+ Map<String, String> actualErrorInfo = null;
+ Map<String, String> leafMap = Maps.newHashMap();
+ for( Entry<String, JsonElement> entry: errorEntryElement.getAsJsonObject().entrySet() ) {
+ String leafName = entry.getKey();
+ JsonElement leafElement = entry.getValue();
+
+ if( "error-info".equals( leafName ) ) {
+ assertNotNull( "Found unexpected \"error-info\" element", errorInfoVerifier );
+ errorInfoElement = leafElement;
+ }
+ else {
+ assertTrue( "\"error\" leaf Json element " + leafName +
+ " is not a Primitive", leafElement.isJsonPrimitive() );
+
+ leafMap.put( leafName, leafElement.getAsString() );
+ }
+ }
+
+ assertEquals( "error-type", expErrorType.getErrorTypeTag(), leafMap.remove( "error-type" ) );
+ assertEquals( "error-tag", expErrorTag.getTagValue(), leafMap.remove( "error-tag" ) );
+
+ verifyOptionalJsonLeaf( leafMap.remove( "error-message" ), expErrorMessage, "error-message" );
+ verifyOptionalJsonLeaf( leafMap.remove( "error-app-tag" ), expErrorAppTag, "error-app-tag" );
+
+ if( !leafMap.isEmpty() ) {
+ fail( "Found unexpected Json leaf elements for \"error\" element: " + leafMap );
+ }
+
+ if( errorInfoVerifier != null ) {
+ assertNotNull( "Missing \"error-info\" element", errorInfoElement );
+ errorInfoVerifier.verifyJson( errorInfoElement );
+ }
+ }
+
+ void verifyOptionalJsonLeaf( String actualValue, String expValue, String tagName ) {
+ if( expValue != null ) {
+ assertEquals( tagName, expValue, actualValue );
+ }
+ else {
+ assertNull( "Found unexpected \"error\" leaf entry for: " + tagName, actualValue );
+ }
+ }
+
+ void verifyXMLResponseBody( InputStream stream, ErrorType expErrorType, ErrorTag expErrorTag,
+ String expErrorMessage, String expErrorAppTag,
+ ErrorInfoVerifier errorInfoVerifier )
+ throws Exception {
+
+ Document doc = parseXMLDocument( stream );
+
+ NodeList children = getXMLErrorList( doc, 1 );
+
+ verifyXMLErrorNode( children.item( 0 ), expErrorType, expErrorTag, expErrorMessage,
+ expErrorAppTag, errorInfoVerifier );
+ }
+
+ private Document parseXMLDocument( InputStream stream ) throws IOException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setCoalescing(true);
+ factory.setIgnoringElementContentWhitespace(true);
+ factory.setIgnoringComments(true);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteStreams.copy( stream, bos );
+
+ System.out.println("XML: "+bos.toString());
+
+ Document doc = null;
+ try {
+ doc = factory.newDocumentBuilder().parse( new ByteArrayInputStream( bos.toByteArray() ) );
+ }
+ catch( Exception e ) {
+ throw new IllegalArgumentException( "Invalid XML response:\n" + bos.toString(), e );
+ }
+ return doc;
+ }
+
+ void verifyXMLErrorNode( Node errorNode, ErrorType expErrorType, ErrorTag expErrorTag,
+ String expErrorMessage, String expErrorAppTag,
+ ErrorInfoVerifier errorInfoVerifier ) throws Exception {
+
+ String errorType = (String)ERROR_TYPE.evaluate( errorNode, XPathConstants.STRING );
+ assertEquals( "error-type", expErrorType.getErrorTypeTag(), errorType );
+
+ String errorTag = (String)ERROR_TAG.evaluate( errorNode, XPathConstants.STRING );
+ assertEquals( "error-tag", expErrorTag.getTagValue(), errorTag );
+
+ verifyOptionalXMLLeaf( errorNode, ERROR_MESSAGE, expErrorMessage, "error-message" );
+ verifyOptionalXMLLeaf( errorNode, ERROR_APP_TAG, expErrorAppTag, "error-app-tag" );
+
+ Node errorInfoNode = (Node)ERROR_INFO.evaluate( errorNode, XPathConstants.NODE );
+ if( errorInfoVerifier != null ) {
+ assertNotNull( "Missing \"error-info\" node", errorInfoNode );
+
+ errorInfoVerifier.verifyXML( errorInfoNode );
+ }
+ else {
+ assertNull( "Found unexpected \"error-info\" node", errorInfoNode );
+ }
+ }
+
+ void verifyOptionalXMLLeaf( Node fromNode, XPathExpression xpath, String expValue,
+ String tagName ) throws Exception {
+ if( expValue != null ) {
+ String actual = (String)xpath.evaluate( fromNode, XPathConstants.STRING );
+ assertEquals( tagName, expValue, actual );
+ }
+ else {
+ assertNull( "Found unexpected \"error\" leaf entry for: " + tagName,
+ xpath.evaluate( fromNode, XPathConstants.NODE ) );
+ }
+ }
+
+ NodeList getXMLErrorList( Node fromNode, int count ) throws Exception {
+ NodeList errorList = (NodeList)ERROR_LIST.evaluate( fromNode, XPathConstants.NODESET );
+ assertNotNull( "Root errors node is empty", errorList );
+ assertEquals( "Root errors node child count", count, errorList.getLength() );
+ return errorList;
+ }
+}
--- /dev/null
+/*
+* Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
+*
+* This program and the accompanying materials are made available under the
+* terms of the Eclipse Public License v1.0 which accompanies this distribution,
+* and is available at http://www.eclipse.org/legal/epl-v10.html
+*/
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import static org.opendaylight.controller.sal.common.util.RpcErrors.getRpcError;
+
+import org.opendaylight.controller.sal.restconf.impl.RestconfError;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcError;
+
+/**
+ * Unit tests for RestconfError.
+ *
+ * @author Devin Avery
+ * @author Thomas Pantelis
+ *
+ */
+public class RestconfErrorTest {
+
+ static class Contains extends BaseMatcher<String> {
+
+ private final String text;
+
+ public Contains( String text ) {
+ this.text = text;
+ }
+
+ @Override
+ public void describeTo( Description desc ) {
+ desc.appendText( "contains " ).appendValue( text );
+ }
+
+ @Override
+ public boolean matches( Object arg ) {
+ return arg != null && arg.toString().contains( text );
+ }
+ }
+
+ @Test
+ public void testErrorTagValueOf()
+ {
+ assertEquals( ErrorTag.IN_USE,
+ ErrorTag.valueOfCaseInsensitive( ErrorTag.IN_USE.getTagValue() ) );
+ }
+
+ @Test
+ public void testErrorTagValueOfIsLowercase()
+ {
+ assertEquals( "in-use",
+ ErrorTag.IN_USE.getTagValue() );
+ }
+
+ @Test
+ public void testErrorTypeGetErrorTypeTagIsLowerCase()
+ {
+ assertEquals( ErrorType.APPLICATION.name().toLowerCase(),
+ ErrorType.APPLICATION.getErrorTypeTag() );
+ }
+
+ @Test
+ public void testErrorTypeValueOf()
+ {
+ assertEquals( ErrorType.APPLICATION,
+ ErrorType.valueOfCaseInsensitive( ErrorType.APPLICATION.getErrorTypeTag() ) );
+ }
+
+ @Test
+ public void testErrorTagStatusCodes()
+ {
+ Map<String,Status> lookUpMap = new HashMap<String,Status>();
+
+ lookUpMap.put( "in-use", Status.fromStatusCode(409));
+ lookUpMap.put( "invalid-value", Status.fromStatusCode(400));
+ lookUpMap.put( "too-big", Status.fromStatusCode(413));
+ lookUpMap.put( "missing-attribute", Status.fromStatusCode(400));
+ lookUpMap.put( "bad-attribute", Status.fromStatusCode(400));
+ lookUpMap.put( "unknown-attribute", Status.fromStatusCode(400));
+ lookUpMap.put( "bad-element", Status.fromStatusCode(400));
+ lookUpMap.put( "unknown-element", Status.fromStatusCode(400));
+ lookUpMap.put( "unknown-namespace", Status.fromStatusCode(400));
+ lookUpMap.put( "access-denied", Status.fromStatusCode(403));
+ lookUpMap.put( "lock-denied", Status.fromStatusCode(409));
+ lookUpMap.put( "resource-denied", Status.fromStatusCode(409));
+ lookUpMap.put( "rollback-failed", Status.fromStatusCode(500));
+ lookUpMap.put( "data-exists", Status.fromStatusCode(409));
+ lookUpMap.put( "data-missing", Status.fromStatusCode(409));
+ lookUpMap.put( "operation-not-supported", Status.fromStatusCode(501));
+ lookUpMap.put( "operation-failed", Status.fromStatusCode(500));
+ lookUpMap.put( "partial-operation", Status.fromStatusCode(500));
+ lookUpMap.put( "malformed-message", Status.fromStatusCode(400));
+
+ for( ErrorTag tag : ErrorTag.values() )
+ {
+ Status expectedStatusCode = lookUpMap.get( tag.getTagValue() );
+ assertNotNull( "Failed to find " + tag.getTagValue(), expectedStatusCode );
+ assertEquals( "Status Code does not match", expectedStatusCode, tag.getStatusCode() );
+ }
+ }
+
+ @Test
+ public void testRestConfDocumentedException_NoCause()
+ {
+ String expectedMessage = "Message";
+ ErrorType expectedErrorType = ErrorType.RPC;
+ ErrorTag expectedErrorTag = ErrorTag.IN_USE;
+ RestconfError e =
+ new RestconfError( expectedErrorType,
+ expectedErrorTag, expectedMessage );
+
+ validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag,
+ null, (String)null, e);
+ }
+
+ @Test
+ public void testRestConfDocumentedException_WithAppTag()
+ {
+ String expectedMessage = "Message";
+ ErrorType expectedErrorType = ErrorType.RPC;
+ ErrorTag expectedErrorTag = ErrorTag.IN_USE;
+ String expectedErrorAppTag = "application.tag";
+
+ RestconfError e =
+ new RestconfError( expectedErrorType,
+ expectedErrorTag, expectedMessage, expectedErrorAppTag );
+
+ validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag,
+ expectedErrorAppTag, (String)null, e);
+ }
+
+ @Test
+ public void testRestConfDocumentedException_WithAppTagErrorInfo()
+ {
+ String expectedMessage = "Message";
+ ErrorType expectedErrorType = ErrorType.RPC;
+ ErrorTag expectedErrorTag = ErrorTag.IN_USE;
+ String expectedErrorAppTag = "application.tag";
+ String errorInfo = "<extra><sessionid>session.id</sessionid></extra>";
+
+ RestconfError e = new RestconfError( expectedErrorType,
+ expectedErrorTag,
+ expectedMessage,
+ expectedErrorAppTag,
+ errorInfo );
+
+ validateRestConfError(expectedMessage, expectedErrorType, expectedErrorTag,
+ expectedErrorAppTag, errorInfo, e);
+ }
+
+ @Test
+ public void testRestConfErrorWithRpcError() {
+
+ // All fields set
+ RpcError rpcError = getRpcError( "mock app-tag", ErrorTag.BAD_ATTRIBUTE.getTagValue(),
+ "mock error-info", RpcError.ErrorSeverity.ERROR,
+ "mock error-message", RpcError.ErrorType.PROTOCOL,
+ new Exception( "mock cause" ) );
+
+ validateRestConfError( "mock error-message", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE,
+ "mock app-tag", "mock error-info",
+ new RestconfError( rpcError ) );
+
+ // All fields set except 'info' - expect error-info set to 'cause'
+ rpcError = getRpcError( "mock app-tag", ErrorTag.BAD_ATTRIBUTE.getTagValue(),
+ null, RpcError.ErrorSeverity.ERROR,
+ "mock error-message", RpcError.ErrorType.PROTOCOL,
+ new Exception( "mock cause" ) );
+
+ validateRestConfError( "mock error-message", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE,
+ "mock app-tag", new Contains( "mock cause" ),
+ new RestconfError( rpcError ) );
+
+ // Some fields set - expect error-info set to ErrorSeverity
+ rpcError = getRpcError( null, ErrorTag.ACCESS_DENIED.getTagValue(),
+ null, RpcError.ErrorSeverity.ERROR,
+ null, RpcError.ErrorType.RPC, null );
+
+ validateRestConfError( null, ErrorType.RPC, ErrorTag.ACCESS_DENIED,
+ null, "<severity>error</severity>",
+ new RestconfError( rpcError ) );
+
+ // 'tag' field not mapped to ErrorTag - expect error-tag set to OPERATION_FAILED
+ rpcError = getRpcError( null, "not mapped",
+ null, RpcError.ErrorSeverity.WARNING,
+ null, RpcError.ErrorType.TRANSPORT, null );
+
+ validateRestConfError( null, ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED,
+ null, "<severity>warning</severity>",
+ new RestconfError( rpcError ) );
+
+ // No fields set - edge case
+ rpcError = getRpcError( null, null, null, null, null, null, null );
+
+ validateRestConfError( null, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED,
+ null, (String)null, new RestconfError( rpcError ) );
+ }
+
+ private void validateRestConfError(String expectedMessage, ErrorType expectedErrorType,
+ ErrorTag expectedErrorTag, String expectedErrorAppTag, String errorInfo, RestconfError e) {
+
+ validateRestConfError( expectedMessage, expectedErrorType, expectedErrorTag,
+ expectedErrorAppTag, equalTo( errorInfo ), e );
+ }
+
+ private void validateRestConfError(String expectedMessage, ErrorType expectedErrorType,
+ ErrorTag expectedErrorTag, String expectedErrorAppTag,
+ Matcher<String> errorInfoMatcher, RestconfError e) {
+
+ assertEquals( "getErrorMessage", expectedMessage, e.getErrorMessage() );
+ assertEquals( "getErrorType", expectedErrorType, e.getErrorType() );
+ assertEquals( "getErrorTag", expectedErrorTag, e.getErrorTag() );
+ assertEquals( "getErrorAppTag", expectedErrorAppTag, e.getErrorAppTag() );
+ assertThat( "getErrorInfo", e.getErrorInfo(), errorInfoMatcher );
+ e.toString(); // really just checking for NPE etc. Don't care about contents.
+ }
+}
import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
-import org.opendaylight.controller.sal.restconf.impl.ResponseException;
+import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
@Test
public void testToInstanceIdentifierListWithNullKey() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes:user/null/boo");
}
@Test
public void testToInstanceIdentifierListWithMissingKey() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes:user/foo");
}
@Test
public void testToInstanceIdentifierChoiceException() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes:food/snack");
}
@Test
public void testToInstanceIdentifierCaseException() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes:food/sports-arena");
}
@Test
public void testToInstanceIdentifierChoiceCaseException() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes:food/snack/sports-arena");
}
-
+
@Test
public void testToInstanceIdentifierWithoutNode() {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
+ exception.expect(RestconfDocumentedException.class);
controllerContext.toInstanceIdentifier("simple-nodes");
}
@Test
public void testMountPointWithoutMountService() throws FileNotFoundException {
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 503 Service Unavailable");
-
+ exception.expect(RestconfDocumentedException.class);
+
controllerContext.setMountService(null);
InstanceIdWithSchemaNode instanceIdentifier = controllerContext
.toInstanceIdentifier("simple-nodes:users/yang-ext:mount/test-interface2:class/student/name");
}
-
+
@Test
public void testMountPointWithoutMountPointSchema() {
initMountService(false);
- exception.expect(ResponseException.class);
- exception.expectMessage("HTTP 400 Bad Request");
-
+ exception.expect(RestconfDocumentedException.class);
+
InstanceIdWithSchemaNode instanceIdentifier = controllerContext
.toInstanceIdentifier("simple-nodes:users/yang-ext:mount/test-interface2:class");
}
-
+
public void initMountService(boolean withSchema) {
MountService mountService = mock(MountService.class);
controllerContext.setMountService(mountService);
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
LOG.info( "Toaster is already making toast" );
RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( null, null, null, null,
- "Toaster is busy", null, null ) ) );
+ RpcErrors.getRpcError( "", "in-use", null, ErrorSeverity.WARNING,
+ "Toaster is busy", ErrorType.APPLICATION, null ) ) );
return Futures.immediateFuture(result);
}
else if( outOfBread() ) {
RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( null, null, null, null,
- "Toaster is out of bread", null, null ) ) );
+ RpcErrors.getRpcError( "out-of-stock", "resource-denied", null, null,
+ "Toaster is out of bread",
+ ErrorType.APPLICATION, null ) ) );
return Futures.immediateFuture(result);
}
else {