/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netconf.sal.rest.impl; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.net.URI; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.ws.rs.Consumes; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.Provider; import org.opendaylight.netconf.sal.rest.api.Draft02; import org.opendaylight.netconf.sal.rest.api.RestconfService; import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext; import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity; import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException; import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Provider @Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON}) public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader { private final static Logger LOG = LoggerFactory.getLogger(JsonToPATCHBodyReader.class); private String patchId; @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } @Override public PATCHContext readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { return readFrom(getInstanceIdentifierContext(), entityStream); } catch (final Exception e) { throw propagateExceptionAs(e); } } private static RuntimeException propagateExceptionAs(Exception e) throws RestconfDocumentedException { if(e instanceof RestconfDocumentedException) { throw (RestconfDocumentedException)e; } if(e instanceof ResultAlreadySetException) { LOG.debug("Error parsing json input:", e); throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. "); } throw new RestconfDocumentedException("Error parsing json input: " + e.getMessage(), ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e); } public PATCHContext readFrom(final String uriPath, final InputStream entityStream) throws RestconfDocumentedException { try { return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream); } catch (final Exception e) { propagateExceptionAs(e); return null; // no-op } } private PATCHContext readFrom(final InstanceIdentifierContext path, final InputStream entityStream) throws IOException { if (entityStream.available() < 1) { return new PATCHContext(path, null, null); } final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream)); final List resultList = read(jsonReader, path); jsonReader.close(); return new PATCHContext(path, resultList, patchId); } private List read(final JsonReader in, InstanceIdentifierContext path) throws IOException { boolean inEdit = false; boolean inValue = false; String operation = null; String target = null; String editId = null; List resultCollection = new ArrayList<>(); while (in.hasNext()) { switch (in.peek()) { case STRING: case NUMBER: in.nextString(); break; case BOOLEAN: Boolean.toString(in.nextBoolean()); break; case NULL: in.nextNull(); break; case BEGIN_ARRAY: in.beginArray(); break; case BEGIN_OBJECT: if (inEdit && operation != null & target != null & inValue) { //let's do the stuff - find out target node // StringInstanceIdentifierCodec codec = new StringInstanceIdentifierCodec(path // .getSchemaContext()); // if (path.getInstanceIdentifier().toString().equals("/")) { // final YangInstanceIdentifier deserialized = codec.deserialize(target); // } DataSchemaNode targetNode = ((DataNodeContainer)(path.getSchemaNode())).getDataChildByName (target.replace("/", "")); if (targetNode == null) { LOG.debug("Target node {} not found in path {} ", target, path.getSchemaNode()); throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } else { final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); //keep on parsing json from place where target points final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), path.getSchemaNode()); jsonParser.parse(in); final YangInstanceIdentifier targetII = path.getInstanceIdentifier().node(targetNode.getQName()); resultCollection.add(new PATCHEntity(editId, operation, targetII, resultHolder.getResult ())); inValue = false; operation = null; target = null; } in.endObject(); } else { in.beginObject(); } break; case END_DOCUMENT: break; case NAME: final String name = in.nextName(); switch (name) { case "edit" : inEdit = true; break; case "operation" : operation = in.nextString(); break; case "target" : target = in.nextString(); break; case "value" : inValue = true; break; case "patch-id" : patchId = in.nextString(); break; case "edit-id" : editId = in.nextString(); break; } break; case END_OBJECT: in.endObject(); break; case END_ARRAY: in.endArray(); break; default: break; } } return ImmutableList.copyOf(resultCollection); } private class StringInstanceIdentifierCodec extends AbstractStringInstanceIdentifierCodec { private final DataSchemaContextTree dataContextTree; private final SchemaContext context; StringInstanceIdentifierCodec(SchemaContext context) { this.context = Preconditions.checkNotNull(context); this.dataContextTree = DataSchemaContextTree.from(context); } @Nonnull @Override protected DataSchemaContextTree getDataContextTree() { return dataContextTree; } @Nullable @Override protected String prefixForNamespace(@Nonnull URI namespace) { final Module module = context.findModuleByNamespaceAndRevision(namespace, null); return module == null ? null : module.getName(); } @Nullable @Override protected QName createQName(@Nonnull String prefix, @Nonnull String localName) { throw new UnsupportedOperationException("Not implemented"); } } }