Merge "Reuse PEM provider in netconf-testtool."
[controller.git] / opendaylight / config / yang-jmx-generator / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / ModuleMXBeanEntryBuilder.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.controller.config.yangjmxgenerator;
9
10 import static com.google.common.base.Preconditions.checkNotNull;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.lang.String.format;
13 import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.createConfigQName;
14
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.base.Function;
17 import com.google.common.base.Optional;
18 import com.google.common.collect.Collections2;
19 import com.google.common.collect.Maps;
20 import com.google.common.collect.Sets;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29 import javax.annotation.Nullable;
30 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AbstractDependencyAttribute;
31 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
32 import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
33 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
34 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
35 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListDependenciesAttribute;
36 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
37 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.rev130405.ServiceRef;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
41 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
42 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.Module;
50 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
51 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
53 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
55 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.UsesNode;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 final class ModuleMXBeanEntryBuilder {
61
62     private Module currentModule;
63     private Map<QName, ServiceInterfaceEntry> qNamesToSIEs;
64     private SchemaContext schemaContext;
65     private TypeProviderWrapper typeProviderWrapper;
66     private String packageName;
67
68     public ModuleMXBeanEntryBuilder setModule(final Module module) {
69         this.currentModule = module;
70         return this;
71     }
72
73     public ModuleMXBeanEntryBuilder setqNamesToSIEs(final Map<QName, ServiceInterfaceEntry> qNamesToSIEs) {
74         this.qNamesToSIEs = qNamesToSIEs;
75         return this;
76     }
77
78     public ModuleMXBeanEntryBuilder setSchemaContext(final SchemaContext schemaContext) {
79         this.schemaContext = schemaContext;
80         return this;
81     }
82
83     public ModuleMXBeanEntryBuilder setTypeProviderWrapper(final TypeProviderWrapper typeProviderWrapper) {
84         this.typeProviderWrapper = typeProviderWrapper;
85         return this;
86     }
87
88     public ModuleMXBeanEntryBuilder setPackageName(final String packageName) {
89         this.packageName = packageName;
90         return this;
91     }
92
93     private static final Logger LOG = LoggerFactory
94             .getLogger(ModuleMXBeanEntryBuilder.class);
95
96     // TODO: the XPath should be parsed by code generator IMO
97     private static final String MAGIC_STRING = "MAGIC_STRING";
98     private static final String MODULE_CONDITION_XPATH_TEMPLATE = "^/MAGIC_STRING:modules/MAGIC_STRING:module/MAGIC_STRING:type\\s*=\\s*['\"](.+)['\"]$";
99     private static final SchemaPath EXPECTED_CONFIGURATION_AUGMENTATION_SCHEMA_PATH = SchemaPath.create(true,
100             createConfigQName("modules"), createConfigQName("module"), createConfigQName("configuration"));
101     private static final SchemaPath EXPECTED_STATE_AUGMENTATION_SCHEMA_PATH = SchemaPath.create(true,
102             createConfigQName("modules"), createConfigQName("module"), createConfigQName("state"));
103     private static final Pattern PREFIX_COLON_LOCAL_NAME = Pattern
104             .compile("^(.+):(.+)$");
105
106
107     public Map<String, ModuleMXBeanEntry> build() {
108         LOG.debug("Generating ModuleMXBeans of {} to package {}",
109                 currentModule.getNamespace(), packageName);
110
111         String configModulePrefix;
112         try {
113             configModulePrefix = getConfigModulePrefixFromImport(currentModule);
114         } catch (IllegalArgumentException e) {
115             // this currentModule does not import config currentModule
116             return Collections.emptyMap();
117         }
118
119         // get identities of base config:currentModule-type
120         Map<String, IdentitySchemaNode> moduleIdentities =  getIdentityMap();
121
122         Map<String, QName> uniqueGeneratedClassesNames = new HashMap<>();
123
124         // each currentModule name should have an augmentation defined
125         Map<String, IdentitySchemaNode> unaugmentedModuleIdentities = new HashMap<>(
126                 moduleIdentities);
127
128         Map<String, ModuleMXBeanEntry> result = new HashMap<>();
129
130         for (AugmentationSchema augmentation : currentModule.getAugmentations()) {
131             Collection<DataSchemaNode> childNodes = augmentation.getChildNodes();
132             if (areAllChildrenChoiceCaseNodes(childNodes)) {
133                 for (ChoiceCaseNode childCase : castChildNodesToChoiceCases(childNodes)) {
134                     // TODO refactor, extract to standalone builder class
135                     processChoiceCaseNode(result, uniqueGeneratedClassesNames, configModulePrefix, moduleIdentities,
136                             unaugmentedModuleIdentities, augmentation, childCase);
137                 }
138             } // skip if child nodes are not all cases
139         }
140         // clean up nulls
141         cleanUpNulls(result);
142         // check attributes name uniqueness
143         checkAttributeNamesUniqueness(uniqueGeneratedClassesNames, result);
144         checkUnaugumentedIdentities(unaugmentedModuleIdentities);
145
146         LOG.debug("Number of ModuleMXBeans to be generated: {}", result.size());
147
148         return result;
149     }
150
151     private static void cleanUpNulls(final Map<String, ModuleMXBeanEntry> result) {
152         for (Map.Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
153             ModuleMXBeanEntry module = entry.getValue();
154             if (module.getAttributes() == null) {
155                 module.setYangToAttributes(Collections
156                         .<String, AttributeIfc> emptyMap());
157             } else if (module.getRuntimeBeans() == null) {
158                 module.setRuntimeBeans(Collections
159                         .<RuntimeBeanEntry> emptyList());
160             }
161         }
162     }
163
164     private static void checkUnaugumentedIdentities(final Map<String, IdentitySchemaNode> unaugmentedModuleIdentities) {
165         if (unaugmentedModuleIdentities.size() > 0) {
166             LOG.warn("Augmentation not found for all currentModule identities: {}",
167                     unaugmentedModuleIdentities.keySet());
168         }
169     }
170
171     private static void checkAttributeNamesUniqueness(final Map<String, QName> uniqueGeneratedClassesNames, final Map<String, ModuleMXBeanEntry> result) {
172         for (Map.Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
173             checkUniqueRuntimeBeanAttributesName(entry.getValue(),
174                     uniqueGeneratedClassesNames);
175         }
176     }
177
178     private Map<String, IdentitySchemaNode> getIdentityMap() {
179         Map<String, IdentitySchemaNode> moduleIdentities = Maps.newHashMap();
180
181         for (IdentitySchemaNode id : currentModule.getIdentities()) {
182             if (id.getBaseIdentity() != null
183                     && ConfigConstants.MODULE_TYPE_Q_NAME.equals(id.getBaseIdentity().getQName())) {
184                 String identityLocalName = id.getQName().getLocalName();
185                 if (moduleIdentities.containsKey(identityLocalName)) {
186                     throw new IllegalStateException("Module name already defined in this currentModule: "
187                             + identityLocalName);
188                 } else {
189                     moduleIdentities.put(identityLocalName, id);
190                     LOG.debug("Found identity {}", identityLocalName);
191                 }
192                 // validation check on unknown schema nodes
193                 boolean providedServiceWasSet = false;
194                 for (UnknownSchemaNode unknownNode : id.getUnknownSchemaNodes()) {
195                     // TODO: test this
196                     boolean unknownNodeIsProvidedServiceExtension = ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME.equals(unknownNode.getNodeType());
197                     // true => no op: 0 or more provided identities are allowed
198
199                     if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME.equals(unknownNode.getNodeType())) {
200                         // 0..1 allowed
201                         checkState(
202                                 providedServiceWasSet == false,
203                                 format("More than one language extension %s is not allowed here: %s",
204                                         ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME, id));
205                         providedServiceWasSet = true;
206                     } else if (unknownNodeIsProvidedServiceExtension == false) {
207                         throw new IllegalStateException("Unexpected language extension " + unknownNode.getNodeType());
208                     }
209                 }
210             }
211         }
212
213         return moduleIdentities;
214     }
215
216     private Collection<ChoiceCaseNode> castChildNodesToChoiceCases(final Collection<DataSchemaNode> childNodes) {
217         return Collections2.transform(childNodes, new Function<DataSchemaNode, ChoiceCaseNode>() {
218             @Nullable
219             @Override
220             public ChoiceCaseNode apply(@Nullable final DataSchemaNode input) {
221                 return (ChoiceCaseNode) input;
222             }
223         });
224     }
225
226     private boolean areAllChildrenChoiceCaseNodes(final Iterable<DataSchemaNode> childNodes) {
227         for (DataSchemaNode childNode : childNodes) {
228             if (childNode instanceof ChoiceCaseNode == false) {
229                 return false;
230             }
231         }
232         return true;
233     }
234
235     private <HAS_CHILDREN_AND_QNAME extends DataNodeContainer & SchemaNode> void processChoiceCaseNode(final Map<String, ModuleMXBeanEntry> result,
236             final Map<String, QName> uniqueGeneratedClassesNames, final String configModulePrefix,
237             final Map<String, IdentitySchemaNode> moduleIdentities,
238             final Map<String, IdentitySchemaNode> unaugmentedModuleIdentities, final AugmentationSchema augmentation,
239             final DataSchemaNode when) {
240
241         ChoiceCaseNode choiceCaseNode = (ChoiceCaseNode) when;
242         if (choiceCaseNode.getConstraints() == null || choiceCaseNode.getConstraints().getWhenCondition() == null) {
243             return;
244         }
245         RevisionAwareXPath xPath = choiceCaseNode.getConstraints().getWhenCondition();
246         Matcher matcher = getWhenConditionMatcher(configModulePrefix, xPath);
247         if (matcher.matches() == false) {
248             return;
249         }
250         String moduleLocalNameFromXPath = matcher.group(1);
251         IdentitySchemaNode moduleIdentity = moduleIdentities.get(moduleLocalNameFromXPath);
252         unaugmentedModuleIdentities.remove(moduleLocalNameFromXPath);
253         checkState(moduleIdentity != null, "Cannot find identity " + moduleLocalNameFromXPath
254                 + " matching augmentation " + augmentation);
255         Map<String, QName> providedServices = findProvidedServices(moduleIdentity, currentModule, qNamesToSIEs,
256                 schemaContext);
257
258         if (moduleIdentity == null) {
259             throw new IllegalStateException("Cannot find identity specified by augmentation xpath constraint: "
260                     + moduleLocalNameFromXPath + " of " + augmentation);
261         }
262         String javaNamePrefix = TypeProviderWrapper.findJavaNamePrefix(moduleIdentity);
263
264         Map<String, AttributeIfc> yangToAttributes = null;
265         // runtime-data
266         Collection<RuntimeBeanEntry> runtimeBeans = null;
267
268         HAS_CHILDREN_AND_QNAME dataNodeContainer = getDataNodeContainer(choiceCaseNode);
269
270         if (EXPECTED_CONFIGURATION_AUGMENTATION_SCHEMA_PATH.equals(augmentation.getTargetPath())) {
271             LOG.debug("Parsing configuration of {}", moduleLocalNameFromXPath);
272             yangToAttributes = fillConfiguration(dataNodeContainer, currentModule, typeProviderWrapper, qNamesToSIEs,
273                     schemaContext, packageName);
274             checkUniqueAttributesWithGeneratedClass(uniqueGeneratedClassesNames, when.getQName(), yangToAttributes);
275         } else if (EXPECTED_STATE_AUGMENTATION_SCHEMA_PATH.equals(augmentation.getTargetPath())) {
276             LOG.debug("Parsing state of {}", moduleLocalNameFromXPath);
277             try {
278                 runtimeBeans = fillRuntimeBeans(dataNodeContainer, currentModule, typeProviderWrapper, packageName,
279                         moduleLocalNameFromXPath, javaNamePrefix);
280             } catch (NameConflictException e) {
281                 throw new NameConflictException(e.getConflictingName(), when.getQName(), when.getQName());
282             }
283             checkUniqueRuntimeBeansGeneratedClasses(uniqueGeneratedClassesNames, when, runtimeBeans);
284             Set<RuntimeBeanEntry> runtimeBeanEntryValues = Sets.newHashSet(runtimeBeans);
285             for (RuntimeBeanEntry entry : runtimeBeanEntryValues) {
286                 checkUniqueAttributesWithGeneratedClass(uniqueGeneratedClassesNames, when.getQName(),
287                         entry.getYangPropertiesToTypesMap());
288             }
289
290         } else {
291             throw new IllegalArgumentException("Cannot parse augmentation " + augmentation);
292         }
293         boolean hasDummyContainer = choiceCaseNode.equals(dataNodeContainer) == false;
294
295         String nullableDummyContainerName = hasDummyContainer ? dataNodeContainer.getQName().getLocalName() : null;
296         if (result.containsKey(moduleLocalNameFromXPath)) {
297             // either fill runtimeBeans or yangToAttributes, merge
298             ModuleMXBeanEntry moduleMXBeanEntry = result.get(moduleLocalNameFromXPath);
299             if (yangToAttributes != null && moduleMXBeanEntry.getAttributes() == null) {
300                 moduleMXBeanEntry.setYangToAttributes(yangToAttributes);
301             } else if (runtimeBeans != null && moduleMXBeanEntry.getRuntimeBeans() == null) {
302                 moduleMXBeanEntry.setRuntimeBeans(runtimeBeans);
303             }
304             checkState(Objects.equals(nullableDummyContainerName, moduleMXBeanEntry.getNullableDummyContainerName()),
305                     "Mismatch in module " + moduleMXBeanEntry.toString() + " - dummy container must be present/missing in" +
306                     " both state and configuration");
307         } else {
308             ModuleMXBeanEntry.ModuleMXBeanEntryInitial initial = new ModuleMXBeanEntry.ModuleMXBeanEntryInitialBuilder()
309                 .setIdSchemaNode(moduleIdentity).setPackageName(packageName).setJavaNamePrefix(javaNamePrefix)
310                 .setNamespace(currentModule.getNamespace().toString()).setqName(ModuleUtil.getQName(currentModule))
311                 .build();
312
313             // construct ModuleMXBeanEntry
314             ModuleMXBeanEntry moduleMXBeanEntry = new ModuleMXBeanEntry(initial, yangToAttributes, providedServices,
315                     runtimeBeans);
316
317             moduleMXBeanEntry.setYangModuleName(currentModule.getName());
318             moduleMXBeanEntry.setYangModuleLocalname(moduleLocalNameFromXPath);
319             moduleMXBeanEntry.setNullableDummyContainerName(nullableDummyContainerName);
320             result.put(moduleLocalNameFromXPath, moduleMXBeanEntry);
321         }
322     }
323
324     private void checkUniqueRuntimeBeansGeneratedClasses(final Map<String, QName> uniqueGeneratedClassesNames,
325             final DataSchemaNode when, final Collection<RuntimeBeanEntry> runtimeBeans) {
326         for (RuntimeBeanEntry runtimeBean : runtimeBeans) {
327             final String javaNameOfRuntimeMXBean = runtimeBean.getJavaNameOfRuntimeMXBean();
328             if (uniqueGeneratedClassesNames.containsKey(javaNameOfRuntimeMXBean)) {
329                 QName firstDefinedQName = uniqueGeneratedClassesNames.get(javaNameOfRuntimeMXBean);
330                 throw new NameConflictException(javaNameOfRuntimeMXBean, firstDefinedQName, when.getQName());
331             }
332             uniqueGeneratedClassesNames.put(javaNameOfRuntimeMXBean, when.getQName());
333         }
334     }
335
336     private static void checkUniqueRuntimeBeanAttributesName(final ModuleMXBeanEntry mxBeanEntry,
337             final Map<String, QName> uniqueGeneratedClassesNames) {
338         for (RuntimeBeanEntry runtimeBeanEntry : mxBeanEntry.getRuntimeBeans()) {
339             for (String runtimeAttName : runtimeBeanEntry.getYangPropertiesToTypesMap().keySet()) {
340                 if (mxBeanEntry.getAttributes().keySet().contains(runtimeAttName)) {
341                     QName qName1 = uniqueGeneratedClassesNames.get(runtimeBeanEntry.getJavaNameOfRuntimeMXBean());
342                     QName qName2 = uniqueGeneratedClassesNames.get(mxBeanEntry.getGloballyUniqueName());
343                     throw new NameConflictException(runtimeAttName, qName1, qName2);
344                 }
345             }
346         }
347     }
348
349     private void checkUniqueAttributesWithGeneratedClass(final Map<String, QName> uniqueGeneratedClassNames,
350             final QName parentQName, final Map<String, AttributeIfc> yangToAttributes) {
351         for (Map.Entry<String, AttributeIfc> attr : yangToAttributes.entrySet()) {
352             if (attr.getValue() instanceof TOAttribute) {
353                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName, (TOAttribute) attr.getValue());
354             } else if (attr.getValue() instanceof ListAttribute
355                     && ((ListAttribute) attr.getValue()).getInnerAttribute() instanceof TOAttribute) {
356                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
357                         (TOAttribute) ((ListAttribute) attr.getValue()).getInnerAttribute());
358             }
359         }
360     }
361
362     private void checkUniqueTOAttr(final Map<String, QName> uniqueGeneratedClassNames, final QName parentQName, final TOAttribute attr) {
363         final String upperCaseCamelCase = attr.getUpperCaseCammelCase();
364         if (uniqueGeneratedClassNames.containsKey(upperCaseCamelCase)) {
365             QName firstDefinedQName = uniqueGeneratedClassNames.get(upperCaseCamelCase);
366             throw new NameConflictException(upperCaseCamelCase, firstDefinedQName, parentQName);
367         } else {
368             uniqueGeneratedClassNames.put(upperCaseCamelCase, parentQName);
369         }
370     }
371
372     private Collection<RuntimeBeanEntry> fillRuntimeBeans(final DataNodeContainer dataNodeContainer, final Module currentModule,
373             final TypeProviderWrapper typeProviderWrapper, final String packageName, final String moduleLocalNameFromXPath,
374             final String javaNamePrefix) {
375
376         return RuntimeBeanEntry.extractClassNameToRuntimeBeanMap(packageName, dataNodeContainer, moduleLocalNameFromXPath,
377                 typeProviderWrapper, javaNamePrefix, currentModule).values();
378
379     }
380
381     /**
382      * Since each case statement within a module must provide unique child nodes, it is allowed to wrap
383      * the actual configuration with a container node with name equal to case name.
384      *
385      * @param choiceCaseNode state or configuration case statement
386      * @return either choiceCaseNode or its only child container
387      */
388     private <HAS_CHILDREN_AND_QNAME extends DataNodeContainer & SchemaNode> HAS_CHILDREN_AND_QNAME getDataNodeContainer(final ChoiceCaseNode choiceCaseNode) {
389         Collection<DataSchemaNode> childNodes = choiceCaseNode.getChildNodes();
390         if (childNodes.size() == 1) {
391             DataSchemaNode onlyChild = childNodes.iterator().next();
392             if (onlyChild instanceof ContainerSchemaNode) {
393                 ContainerSchemaNode onlyContainer = (ContainerSchemaNode) onlyChild;
394                 if (Objects.equals(onlyContainer.getQName().getLocalName(), choiceCaseNode.getQName().getLocalName())) {
395                     // the actual configuration is inside dummy container
396                     return (HAS_CHILDREN_AND_QNAME) onlyContainer;
397                 }
398             }
399         }
400         return (HAS_CHILDREN_AND_QNAME) choiceCaseNode;
401     }
402
403     private Map<String, AttributeIfc> fillConfiguration(final DataNodeContainer dataNodeContainer, final Module currentModule,
404             final TypeProviderWrapper typeProviderWrapper, final Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
405             final SchemaContext schemaContext, final String packageName) {
406         Map<String, AttributeIfc> yangToAttributes = new HashMap<>();
407         for (DataSchemaNode attrNode : dataNodeContainer.getChildNodes()) {
408             AttributeIfc attributeValue = getAttributeValue(attrNode, currentModule, qNamesToSIEs, typeProviderWrapper,
409                     schemaContext, packageName);
410             yangToAttributes.put(attributeValue.getAttributeYangName(), attributeValue);
411         }
412         return yangToAttributes;
413     }
414
415     private Map<String, QName> findProvidedServices(final IdentitySchemaNode moduleIdentity, final Module currentModule,
416             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final SchemaContext schemaContext) {
417         Map<String, QName> result = new HashMap<>();
418         for (UnknownSchemaNode unknownNode : moduleIdentity.getUnknownSchemaNodes()) {
419             if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME.equals(unknownNode.getNodeType())) {
420                 String prefixAndIdentityLocalName = unknownNode.getNodeParameter();
421                 ServiceInterfaceEntry sie = findSIE(prefixAndIdentityLocalName, currentModule, qNamesToSIEs,
422                         schemaContext);
423                 result.put(sie.getFullyQualifiedName(), sie.getQName());
424             }
425         }
426         return result;
427     }
428
429     private AttributeIfc getAttributeValue(final DataSchemaNode attrNode, final Module currentModule,
430             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final TypeProviderWrapper typeProviderWrapper,
431             final SchemaContext schemaContext, final String packageName) {
432
433         if (attrNode instanceof LeafSchemaNode) {
434             // simple type
435             LeafSchemaNode leaf = (LeafSchemaNode) attrNode;
436             return new JavaAttribute(leaf, typeProviderWrapper);
437         } else if (attrNode instanceof ContainerSchemaNode) {
438             // reference or TO
439             ContainerSchemaNode containerSchemaNode = (ContainerSchemaNode) attrNode;
440             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
441                     containerSchemaNode, attrNode, currentModule, qNamesToSIEs, schemaContext);
442             if (dependencyAttributeOptional.isPresent()) {
443                 return dependencyAttributeOptional.get();
444             } else {
445                 return TOAttribute.create(containerSchemaNode, typeProviderWrapper, packageName);
446             }
447
448         } else if (attrNode instanceof LeafListSchemaNode) {
449             return ListAttribute.create((LeafListSchemaNode) attrNode, typeProviderWrapper);
450         } else if (attrNode instanceof ListSchemaNode) {
451             ListSchemaNode listSchemaNode = (ListSchemaNode) attrNode;
452             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
453                     listSchemaNode, attrNode, currentModule, qNamesToSIEs, schemaContext);
454             if (dependencyAttributeOptional.isPresent()) {
455                 return dependencyAttributeOptional.get();
456             } else {
457                 return ListAttribute.create(listSchemaNode, typeProviderWrapper, packageName);
458             }
459         } else {
460             throw new UnsupportedOperationException("Unknown configuration node " + attrNode.toString());
461         }
462     }
463
464     private Optional<? extends AbstractDependencyAttribute> extractDependency(final DataNodeContainer dataNodeContainer,
465             final DataSchemaNode attrNode, final Module currentModule, final Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
466             final SchemaContext schemaContext) {
467         if (isDependencyContainer(dataNodeContainer)) {
468             // reference
469             UsesNode usesNode = dataNodeContainer.getUses().iterator().next();
470             checkState(usesNode.getRefines().size() == 1, "Unexpected 'refine' child node size of " + dataNodeContainer);
471             LeafSchemaNode refine = (LeafSchemaNode) usesNode.getRefines().values().iterator().next();
472             checkState(refine.getUnknownSchemaNodes().size() == 1, "Unexpected unknown schema node size of " + refine);
473             UnknownSchemaNode requiredIdentity = refine.getUnknownSchemaNodes().iterator().next();
474             checkState(ConfigConstants.REQUIRED_IDENTITY_EXTENSION_QNAME.equals(requiredIdentity.getNodeType()),
475                     "Unexpected language extension " + requiredIdentity);
476             String prefixAndIdentityLocalName = requiredIdentity.getNodeParameter();
477             // import should point to a module
478             ServiceInterfaceEntry serviceInterfaceEntry = findSIE(prefixAndIdentityLocalName, currentModule,
479                     qNamesToSIEs, schemaContext);
480             boolean mandatory = refine.getConstraints().isMandatory();
481             AbstractDependencyAttribute reference;
482             if (dataNodeContainer instanceof ContainerSchemaNode) {
483                 reference = new DependencyAttribute(attrNode, serviceInterfaceEntry, mandatory,
484                         attrNode.getDescription());
485             } else {
486                 reference = new ListDependenciesAttribute(attrNode, serviceInterfaceEntry, mandatory,
487                         attrNode.getDescription());
488             }
489             return Optional.of(reference);
490         }
491         return Optional.absent();
492     }
493
494     private boolean isDependencyContainer(final DataNodeContainer dataNodeContainer) {
495         if(dataNodeContainer.getUses().size() != 1) {
496             return false;
497         }
498         UsesNode onlyUses = dataNodeContainer.getUses().iterator().next();
499         if(onlyUses.getGroupingPath().getLastComponent().equals(ServiceRef.QNAME) == false) {
500             return false;
501         }
502
503         return getChildNodeSizeWithoutUses(dataNodeContainer) == 0;
504     }
505
506     private int getChildNodeSizeWithoutUses(final DataNodeContainer csn) {
507         int result = 0;
508         for (DataSchemaNode dsn : csn.getChildNodes()) {
509             if (dsn.isAddedByUses() == false) {
510                 result++;
511             }
512         }
513         return result;
514     }
515
516     private ServiceInterfaceEntry findSIE(final String prefixAndIdentityLocalName, final Module currentModule,
517             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final SchemaContext schemaContext) {
518
519         Matcher m = PREFIX_COLON_LOCAL_NAME.matcher(prefixAndIdentityLocalName);
520         Module foundModule;
521         String localSIName;
522         if (m.matches()) {
523             // if there is a prefix, look for ModuleImport with this prefix. Get
524             // Module from SchemaContext
525             String prefix = m.group(1);
526             ModuleImport moduleImport = findModuleImport(currentModule, prefix);
527             foundModule = schemaContext.findModuleByName(moduleImport.getModuleName(), moduleImport.getRevision());
528             checkNotNull(foundModule, format("Module not found in SchemaContext by %s", moduleImport));
529             localSIName = m.group(2);
530         } else {
531             foundModule = currentModule; // no prefix => SIE is in currentModule
532             localSIName = prefixAndIdentityLocalName;
533         }
534         QName siQName = QName.create(foundModule.getNamespace(), foundModule.getRevision(), localSIName);
535         ServiceInterfaceEntry sie = qNamesToSIEs.get(siQName);
536         checkState(sie != null, "Cannot find referenced Service Interface by " + prefixAndIdentityLocalName);
537         return sie;
538     }
539
540     private ModuleImport findModuleImport(final Module module, final String prefix) {
541         for (ModuleImport moduleImport : module.getImports()) {
542             if (moduleImport.getPrefix().equals(prefix)) {
543                 return moduleImport;
544             }
545         }
546         throw new IllegalStateException(format("Import not found with prefix %s in %s", prefix, module));
547     }
548
549     @VisibleForTesting
550     static Matcher getWhenConditionMatcher(final String prefix, final RevisionAwareXPath whenConstraint) {
551         String xpathRegex = MODULE_CONDITION_XPATH_TEMPLATE.replace(MAGIC_STRING, prefix);
552         Pattern pattern = Pattern.compile(xpathRegex);
553         return pattern.matcher(whenConstraint.toString());
554     }
555
556     String getConfigModulePrefixFromImport(final Module currentModule) {
557         for (ModuleImport currentImport : currentModule.getImports()) {
558             if (currentImport.getModuleName().equals(ConfigConstants.CONFIG_MODULE)) {
559                 return currentImport.getPrefix();
560             }
561         }
562         throw new IllegalArgumentException("Cannot find import " + ConfigConstants.CONFIG_MODULE + " in "
563                 + currentModule);
564     }
565
566 }