BUG-579: improve code readability in RestconfUtils.
[yangtools.git] / restconf / restconf-util / src / main / java / org / opendaylight / yangtools / restconf / utils / RestconfUtils.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.restconf.utils;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.BiMap;
13 import com.google.common.collect.HashBiMap;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.net.URI;
18 import java.net.URLEncoder;
19 import java.util.AbstractMap.SimpleEntry;
20 import java.util.Arrays;
21 import java.util.Date;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import javax.xml.parsers.DocumentBuilder;
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
30 import org.opendaylight.yangtools.yang.binding.DataObject;
31 import org.opendaylight.yangtools.yang.binding.RpcService;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
37 import org.opendaylight.yangtools.yang.data.api.Node;
38 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
39 import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
40 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
41 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider;
42 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.Module;
48 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.w3c.dom.Document;
52 import org.w3c.dom.Element;
53 import org.xml.sax.SAXException;
54
55 public class RestconfUtils {
56
57     private static final Logger logger = LoggerFactory.getLogger(RestconfUtils.class);
58
59     private static final BiMap<URI, String> uriToModuleName = HashBiMap.<URI, String> create();
60
61     public static Entry<String, DataSchemaNode> toRestconfIdentifier(
62             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<?> bindingIdentifier,
63             final BindingIndependentMappingService mappingService, final SchemaContext schemaContext) {
64         InstanceIdentifier domIdentifier = mappingService.toDataDom(bindingIdentifier);
65         return toRestconfIdentifier(domIdentifier, schemaContext);
66     }
67
68     public static Entry<String, DataSchemaNode> toRestconfIdentifier(final InstanceIdentifier xmlInstanceIdentifier,
69             final SchemaContext schemaContext) {
70
71         final Iterable<InstanceIdentifier.PathArgument> elements = xmlInstanceIdentifier.getPathArguments();
72         final StringBuilder ret = new StringBuilder();
73         final QName startQName = elements.iterator().next().getNodeType();
74         URI namespace = startQName.getNamespace();
75         Date revision = startQName.getRevision();
76         final Module initialModule = schemaContext.findModuleByNamespaceAndRevision(namespace, revision);
77         DataNodeContainer node = (initialModule);
78         DataSchemaNode schemaNode = null;
79         for (final InstanceIdentifier.PathArgument element : elements) {
80             final DataSchemaNode potentialNode = node.getDataChildByName(element.getNodeType());
81             if (!isListOrContainer(potentialNode)) {
82                 return null;
83             }
84             node = ((DataNodeContainer) potentialNode);
85             schemaNode = potentialNode;
86             ret.append(convertToRestconfIdentifier(element, node, schemaContext));
87         }
88         return new SimpleEntry<>(ret.toString(), schemaNode);
89     }
90
91     private static CharSequence convertContainerToRestconfIdentifier(final InstanceIdentifier.NodeIdentifier argument,
92             final SchemaContext schemaContext) {
93         return "/" + toRestconfIdentifier(argument.getNodeType(), schemaContext);
94     }
95
96     private static CharSequence convertListToRestconfIdentifier(
97             final InstanceIdentifier.NodeIdentifierWithPredicates argument, final ListSchemaNode node,
98             final SchemaContext schemaContext) {
99         QName _nodeType = argument.getNodeType();
100         final CharSequence nodeIdentifier = toRestconfIdentifier(_nodeType, schemaContext);
101         final Map<QName, Object> keyValues = argument.getKeyValues();
102
103         StringBuilder sb = new StringBuilder('/');
104         sb.append(nodeIdentifier);
105         sb.append('/');
106
107         List<QName> keyDefinition = node.getKeyDefinition();
108         boolean _hasElements = false;
109         for (final QName key : keyDefinition) {
110             if (_hasElements) {
111                 sb.append('/');
112             } else {
113                 _hasElements = true;
114             }
115             Object _get = keyValues.get(key);
116             sb.append(toUriString(_get));
117         }
118
119         return sb.toString();
120     }
121
122     private static String toUriString(final Object object) {
123         if (object == null) {
124             return "";
125         }
126         String _string = object.toString();
127         return URLEncoder.encode(_string);
128     }
129
130     public static CharSequence toRestconfIdentifier(final QName qname, final SchemaContext schemaContext) {
131         URI namespace = qname.getNamespace();
132         String module = uriToModuleName.get(namespace);
133         if (module == null) {
134             Date revision = qname.getRevision();
135             final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision(namespace, revision);
136             if (moduleSchema == null) {
137                 return null;
138             }
139             String name = moduleSchema.getName();
140             uriToModuleName.put(namespace, name);
141             module = name;
142         }
143         return module + ':' + qname.getLocalName();
144     }
145
146     private static CharSequence convertToRestconfIdentifier(final InstanceIdentifier.PathArgument argument,
147             final DataNodeContainer node, final SchemaContext schemaContext) {
148         if (argument instanceof InstanceIdentifier.NodeIdentifier) {
149             return convertContainerToRestconfIdentifier((NodeIdentifier) argument, schemaContext);
150         } else if (argument instanceof InstanceIdentifier.NodeIdentifierWithPredicates
151                 && node instanceof ListSchemaNode) {
152             return convertListToRestconfIdentifier((NodeIdentifierWithPredicates) argument, (ListSchemaNode) node,
153                     schemaContext);
154         } else {
155             throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.asList(argument, node).toString());
156         }
157     }
158
159     private static boolean isListOrContainer(final DataSchemaNode node) {
160         if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
161             return true;
162         }
163         return false;
164     }
165
166     public static Module findModuleByNamespace(final URI namespace, final SchemaContext schemaContext) {
167         Preconditions.checkArgument(namespace != null);
168         final Set<Module> moduleSchemas = schemaContext.findModuleByNamespace(namespace);
169         Module filterLatestModule = null;
170         if (moduleSchemas != null) {
171             filterLatestModule = filterLatestModule(moduleSchemas);
172         }
173         return filterLatestModule;
174     }
175
176     private static Module filterLatestModule(final Iterable<Module> modules) {
177         Module latestModule = Iterables.getFirst(modules, null);
178         for (final Module module : modules) {
179             Date revision = module.getRevision();
180             Date latestModuleRevision = latestModule.getRevision();
181             if (revision.after(latestModuleRevision)) {
182                 latestModule = module;
183             }
184         }
185         return latestModule;
186     }
187
188     public String findModuleNameByNamespace(final URI namespace, final SchemaContext schemaContext) {
189         String moduleName = uriToModuleName.get(namespace);
190         if (moduleName == null) {
191             final Module module = findModuleByNamespace(namespace, schemaContext);
192             if (module == null) {
193                 return null;
194             }
195             moduleName = module.getName();
196             uriToModuleName.put(namespace, moduleName);
197         }
198         return moduleName;
199     }
200
201     /**
202      * Parse rpc services from input stream.
203      *
204      * @param inputStream
205      *            stream containing rpc services definition in xml
206      *            representation
207      * @param mappingService
208      *            current mapping service
209      * @param schemaContext
210      *            parsed yang data context
211      * @return Set of classes representing rpc services parsed from input stream
212      */
213     public static Set<Class<? extends RpcService>> rpcServicesFromInputStream(final InputStream inputStream,
214             final BindingIndependentMappingService mappingService, final SchemaContext schemaContext) {
215         try {
216             DocumentBuilderFactory documentBuilder = DocumentBuilderFactory.newInstance();
217             documentBuilder.setNamespaceAware(true);
218             DocumentBuilder builder = documentBuilder.newDocumentBuilder();
219             Document doc = builder.parse(inputStream);
220             Element rootElement = doc.getDocumentElement();
221
222             List<Node<?>> domNodes = XmlDocumentUtils.toDomNodes(rootElement,
223                     Optional.of(schemaContext.getChildNodes()));
224             Set<Class<? extends RpcService>> rpcServices = new HashSet<Class<? extends RpcService>>();
225             for (Node<?> node : domNodes) {
226                 if (node instanceof ImmutableCompositeNode) {
227                     ImmutableCompositeNode icNode = (ImmutableCompositeNode) node;
228                     QName namespace = null;
229                     QName revision = null;
230                     QName name = null;
231                     for (QName q : icNode.keySet()) {
232                         if (q.getLocalName().equals("namespace")) {
233                             namespace = q;
234                         }
235                         if (q.getLocalName().equals("revision")) {
236                             revision = q;
237                         }
238                         if (q.getLocalName().equals("name")) {
239                             name = q;
240                         }
241
242                     }
243
244                     // FIXME: Method getRpcServiceClassFor has been modified and
245                     // fixed to follow API contract. This call MUST be updated
246                     // to follow contract i.e. pass correct parameters:
247                     // "NAMESPACE" and "REVISION"
248                     Optional<Class<? extends RpcService>> rpcService = mappingService.getRpcServiceClassFor(
249                             icNode.get(name).get(0).getValue().toString(), icNode.get(revision).get(0).getValue()
250                                     .toString());
251                     if (rpcService.isPresent()) {
252                         rpcServices.add(rpcService.get());
253                     }
254                 }
255             }
256
257             return rpcServices;
258         } catch (ParserConfigurationException e) {
259             logger.trace("Parse configuration exception {}", e);
260         } catch (SAXException e) {
261             logger.trace("SAX exception {}", e);
262         } catch (IOException e) {
263             logger.trace("IOException {}", e);
264         }
265         return null;
266     }
267
268     /**
269      * Parse DataObject from input stream.
270      *
271      * @param path
272      *            identifier of expected result object
273      * @param inputStream
274      *            stream containing xml data to parse
275      * @param schemaContext
276      *            parsed yang data context
277      * @param mappingService
278      *            current mapping service
279      * @param dataSchema
280      *            yang data schema node representation of resulting data object
281      * @return DataObject instance parsed from input stream
282      */
283     public static DataObject dataObjectFromInputStream(
284             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<?> path, final InputStream inputStream,
285             final SchemaContext schemaContext, final BindingIndependentMappingService mappingService,
286             final DataSchemaNode dataSchema) {
287         // Parse stream into w3c Document
288         try {
289             DocumentBuilderFactory documentBuilder = DocumentBuilderFactory.newInstance();
290             documentBuilder.setNamespaceAware(true);
291             DocumentBuilder builder = documentBuilder.newDocumentBuilder();
292             Document doc = builder.parse(inputStream);
293             Element rootElement = doc.getDocumentElement();
294             Node<?> domNode = XmlDocumentUtils.toDomNode(rootElement, Optional.of(dataSchema),
295                     Optional.<XmlCodecProvider> absent());
296             DataObject dataObject = mappingService.dataObjectFromDataDom(path, (CompositeNode) domNode); // getDataFromResponse
297             return dataObject;
298         } catch (DeserializationException e) {
299             logger.trace("Deserialization exception {}", e);
300         } catch (ParserConfigurationException e) {
301             logger.trace("Parse configuration exception {}", e);
302         } catch (SAXException e) {
303             logger.trace("SAX exception {}", e);
304         } catch (IOException e) {
305             logger.trace("IOException {}", e);
306         }
307         return null;
308     }
309
310 }