Skip to content

Commit 214e610

Browse files
authored
Add methods to read/write multiple-document YAML files (#1085)
1 parent 1ffcb74 commit 214e610

File tree

7 files changed

+186
-63
lines changed

7 files changed

+186
-63
lines changed

core/src/main/java/oracle/weblogic/deploy/yaml/AbstractYamlTranslator.java

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,55 +39,84 @@ public abstract class AbstractYamlTranslator {
3939

4040
protected abstract PlatformLogger getLogger();
4141
protected abstract String getClassName();
42-
public abstract PyDictionary parse() throws YamlException;
42+
43+
// override to return a list of documents as Python dictionaries from the YAML
44+
public abstract PyList parseDocuments(boolean allowMultiple) throws YamlException;
45+
46+
// override to write a list of documents as Python dictionaries to the YAML
47+
public abstract void dumpDocuments(List<?> documents) throws YamlException;
4348

4449
protected AbstractYamlTranslator(String fileName, boolean useOrderedDict) {
4550
this.fileName = fileName;
4651
this.useOrderedDict = useOrderedDict;
4752
}
4853

54+
/**
55+
* This method triggers parsing of the file and conversion into a Python dictionary.
56+
* If the YAML is empty, a single empty dictionary is returned.
57+
* If the YAML contains multiple documents, a YamlException is thrown
58+
*
59+
* @return the python dictionary corresponding to the YAML input file
60+
* @throws YamlException if an error occurs while reading the input file
61+
*/
62+
public PyDictionary parse() throws YamlException {
63+
final String METHOD = "parse";
64+
PyDictionary result;
65+
PyList dictionaries = parseDocuments(false);
66+
if (dictionaries.isEmpty()) {
67+
result = getNewDictionary();
68+
} else {
69+
result = (PyDictionary) dictionaries.get(0);
70+
}
71+
return result;
72+
}
73+
4974
@SuppressWarnings("WeakerAccess")
50-
protected PyDictionary parseInternal(InputStream inputStream) throws YamlException {
75+
protected PyList parseInternal(InputStream inputStream, boolean allowMultiple) throws YamlException {
5176
final String METHOD = "parseInternal";
5277

53-
PyDictionary result = null;
78+
// there are problems using PyList.add(),
79+
// so build a java.util.List and construct PyList(javaList).
80+
List<PyObject> result = new ArrayList<>();
5481
if (inputStream != null) {
5582
Yaml parser = new Yaml(this.getDefaultLoaderOptions());
5683

57-
PyDictionary firstDoc = null;
58-
int docCount = 0;
5984
try {
6085
Iterable<Object> docsIterable = parser.loadAll(inputStream);
61-
62-
Object docToConvert = null;
86+
List<Object> documents = new ArrayList<>();
6387
for (Object document : docsIterable) {
64-
docCount++;
65-
if (docCount == 1) {
66-
docToConvert = document;
67-
}
88+
documents.add(document);
6889
}
69-
if (docCount == 1) {
70-
firstDoc = convertJavaDataStructureToPython(docToConvert);
71-
} else if (docCount == 0) {
72-
firstDoc = getNewDictionary();
90+
91+
// don't continue with conversion if multiple documents check fails
92+
if(!allowMultiple && documents.size() > 1) {
93+
YamlException pex = new YamlException("WLSDPLY-18101", this.fileName, documents.size());
94+
getLogger().throwing(getClassName(), METHOD, pex);
95+
throw pex;
7396
}
97+
98+
for (Object document : documents) {
99+
result.add(convertJavaDataStructureToPython(document));
100+
}
101+
} catch (YamlException yex) {
102+
throw yex;
74103
} catch (Exception ex) {
75104
YamlException pex = new YamlException("WLSDPLY-18100", ex, this.fileName, ex.getLocalizedMessage());
76105
getLogger().throwing(getClassName(), METHOD, pex);
77106
throw pex;
78107
}
79-
if (docCount > 1) {
80-
YamlException pex = new YamlException("WLSDPLY-18101", this.fileName, docCount);
81-
getLogger().throwing(getClassName(), METHOD, pex);
82-
throw pex;
83-
}
84-
result = firstDoc;
85108
}
86-
return result;
109+
return new PyList(result.toArray(new PyObject[0]));
110+
}
111+
112+
public void dump(Map<String, Object> data) throws YamlException {
113+
List<Map<String, Object>> list = new ArrayList<>();
114+
list.add(data);
115+
dumpDocuments(list);
87116
}
88117

89118
@SuppressWarnings("WeakerAccess")
90-
protected void dumpInternal(Map<String, Object> data, Writer outputWriter) throws YamlException {
119+
protected void dumpInternal(List<?> data, Writer outputWriter) throws YamlException {
91120
final String METHOD = "dumpInternal";
92121

93122
if (outputWriter != null) {
@@ -96,7 +125,7 @@ protected void dumpInternal(Map<String, Object> data, Writer outputWriter) throw
96125
Yaml yaml = new Yaml(representer, dumperOptions);
97126

98127
try {
99-
yaml.dump(replaceNoneInMap(data), outputWriter);
128+
yaml.dumpAll(data.iterator(), outputWriter);
100129
} catch (Exception ex) {
101130
YamlException pex = new YamlException("WLSDPLY-18107", ex, this.fileName, ex.getLocalizedMessage());
102131
getLogger().throwing(getClassName(), METHOD, pex);

core/src/main/java/oracle/weblogic/deploy/yaml/YamlStreamTranslator.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
import java.io.IOException;
88
import java.io.InputStream;
99
import java.io.Writer;
10-
import java.util.Map;
10+
import java.util.List;
1111

1212
import oracle.weblogic.deploy.logging.PlatformLogger;
1313
import oracle.weblogic.deploy.logging.WLSDeployLogFactory;
1414

15-
import org.python.core.PyDictionary;
15+
import org.python.core.PyList;
1616

1717
/**
1818
* An implementation of the YAML parser/translator that reads the YAML input from an input stream.
@@ -63,21 +63,21 @@ public YamlStreamTranslator(String streamFileName, Writer yamlOutputWriter) {
6363
}
6464

6565
/**
66-
* This method triggers parsing of the YAML and conversion into the Python dictionary. Note that is closes
67-
* the input stream when it is finished, making the instance no longer viable.
66+
* Read a list of documents as Python dictionaries from the YAML input stream.
67+
* Note that the input stream is closed when it is finished, making the instance no longer viable.
6868
*
69-
* @return the python dictionary corresponding to the YAML input
69+
* @return a list of Python dictionaries corresponding to the YAML input
7070
* @throws YamlException if an error occurs while reading the input
7171
*/
7272
@Override
73-
public PyDictionary parse() throws YamlException {
74-
final String METHOD = "parse";
73+
public PyList parseDocuments(boolean allowMultiple) throws YamlException {
74+
final String METHOD = "parseDocuments";
7575

7676
LOGGER.entering(CLASS, METHOD);
77-
PyDictionary result = null;
77+
PyList result = null;
7878
if (yamlStream != null) {
7979
try {
80-
result = parseInternal(yamlStream);
80+
result = parseInternal(yamlStream, allowMultiple);
8181
} finally {
8282
try {
8383
yamlStream.close();
@@ -91,8 +91,8 @@ public PyDictionary parse() throws YamlException {
9191
return result;
9292
}
9393

94-
public void dump(Map<String, Object> data) throws YamlException {
95-
final String METHOD = "dump";
94+
public void dumpDocuments(List<?> data) throws YamlException {
95+
final String METHOD = "dumpDocuments";
9696

9797
LOGGER.entering(CLASS, METHOD);
9898
if (yamlOutputWriter != null) {

core/src/main/java/oracle/weblogic/deploy/yaml/YamlTranslator.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import java.io.FileInputStream;
99
import java.io.FileWriter;
1010
import java.io.IOException;
11-
import java.util.Map;
11+
import java.util.List;
1212

1313
import oracle.weblogic.deploy.logging.PlatformLogger;
1414
import oracle.weblogic.deploy.logging.WLSDeployLogFactory;
1515
import oracle.weblogic.deploy.util.FileUtils;
1616

17-
import org.python.core.PyDictionary;
17+
import org.python.core.PyList;
1818

1919
/**
2020
* An implementation of the YAML parser/translator that reads the YAML input from an input stream.
@@ -46,20 +46,21 @@ public YamlTranslator(String fileName, boolean useOrderedDict) {
4646
super(fileName, useOrderedDict);
4747
this.yamlFile = FileUtils.validateExistingFile(fileName);
4848
}
49+
4950
/**
50-
* This method triggers parsing of the file and conversion into the Python dictionary.
51+
* Read a list of documents as Python dictionaries from the YAML file.
5152
*
52-
* @return the python dictionary corresponding to the YAML input file
53-
* @throws YamlException if an error occurs while reading the input file
53+
* @return a list of documents corresponding to the YAML input
54+
* @throws YamlException if an error occurs while reading the input
5455
*/
5556
@Override
56-
public PyDictionary parse() throws YamlException {
57-
final String METHOD = "parse";
57+
public PyList parseDocuments(boolean allowMultiple) throws YamlException {
58+
final String METHOD = "parseDocuments";
5859

5960
LOGGER.entering(CLASS, METHOD);
60-
PyDictionary result;
61+
PyList result;
6162
try (FileInputStream fis = new FileInputStream(yamlFile)) {
62-
result = parseInternal(fis);
63+
result = parseInternal(fis, allowMultiple);
6364
} catch (IOException ioe) {
6465
YamlException ex = new YamlException("WLSDPLY-18108", ioe, yamlFile.getPath(), ioe.getLocalizedMessage());
6566
LOGGER.throwing(CLASS, METHOD, ex);
@@ -71,8 +72,8 @@ public PyDictionary parse() throws YamlException {
7172
return result;
7273
}
7374

74-
public void dump(Map<String, Object> data) throws YamlException {
75-
final String METHOD = "dump";
75+
public void dumpDocuments(List<?> data) throws YamlException {
76+
final String METHOD = "dumpDocuments";
7677

7778
// Don't log the data since it is big and could contain credentials.
7879
LOGGER.entering(CLASS, METHOD);

core/src/main/python/compare_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def compare(self):
209209
print format_message('WLSDPLY-05707')
210210
print BLANK_LINE
211211
pty = PythonToYaml(change_model)
212-
pty._write_dictionary_to_yaml_file(change_model, System.out)
212+
pty.write_to_stream(System.out)
213213

214214
return 0
215215

core/src/main/python/wlsdeploy/yaml/yaml_translator.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.lang.Long as JLong
1515
import java.lang.String as JString
1616
import java.util.ArrayList as JArrayList
17+
from java.io import OutputStreamWriter
1718

1819
import oracle.weblogic.deploy.util.FileUtils as JFileUtils
1920
import oracle.weblogic.deploy.yaml.YamlStreamTranslator as JYamlStreamTranslator
@@ -63,6 +64,22 @@ def parse(self):
6364
self._logger.exiting(class_name=self._class_name, method_name=_method_name)
6465
return result_dict
6566

67+
def parse_documents(self):
68+
"""
69+
Parse the Yaml content from the file and convert it to a list of Python dictionaries.
70+
:return: a list of documents as Python dictionaries
71+
:raises: YamlException: if an error occurs while parsing the Yaml or converting it to the list
72+
"""
73+
_method_name = 'parse_documents'
74+
75+
self._logger.entering(class_name=self._class_name, method_name=_method_name)
76+
# throws YamlException with details, nothing we can really add here...
77+
result_dict = self._translator.parseDocuments(True)
78+
79+
# don't log the model on exit, it may contain passwords
80+
self._logger.exiting(class_name=self._class_name, method_name=_method_name)
81+
return result_dict
82+
6683

6784
class YamlStreamToPython(object):
6885
"""
@@ -102,21 +119,24 @@ def parse(self):
102119

103120
class PythonToJava(object):
104121
"""
105-
A class that converts a Python dictionary and its contents to the Java types expected by snakeyaml.
122+
A class that converts a Python dictionary or document list and its contents
123+
to the Java types expected by snakeyaml.
106124
"""
107125
_class_name = 'PythonToJava'
108126

109-
def __init__(self, dictionary):
110-
self._dictionary = dictionary
127+
def __init__(self, collection):
128+
self._collection = collection
111129
self._logger = PlatformLogger('wlsdeploy.yaml')
112130

113131
def convert_to_java(self):
114132
_method_name = 'convert_to_java'
115133

116-
if self._dictionary is None:
134+
if self._collection is None:
117135
return None
118-
if isinstance(self._dictionary, dict):
119-
return self.convert_dict_to_java_map(self._dictionary)
136+
if isinstance(self._collection, dict):
137+
return self.convert_dict_to_java_map(self._collection)
138+
if isinstance(self._collection, list):
139+
return self.convert_list_to_java_list(self._collection)
120140
else:
121141
yaml_ex = exception_helper.create_yaml_exception('WLSDPLY-18200')
122142
self._logger.throwing(class_name=self._class_name, method_name=_method_name, error=yaml_ex)
@@ -181,13 +201,13 @@ def convert_scalar_to_java_type(self, py_value):
181201

182202
class PythonToYaml(object):
183203
"""
184-
A class that converts a Python dictionary into Yaml and writes the output to a file.
204+
A class that converts a Python dictionary or document list into Yaml and writes the output to a file.
185205
"""
186206
_class_name = 'PythonToYaml'
187207

188-
def __init__(self, dictionary):
208+
def __init__(self, collection):
189209
# Fix error handling for None
190-
self._dictionary = dictionary
210+
self._collection = collection
191211
self._logger = PlatformLogger('wlsdeploy.yaml')
192212
return
193213

@@ -212,7 +232,7 @@ def write_to_yaml_file(self, file_name):
212232
writer = None
213233
try:
214234
writer = JFileWriter(yaml_file)
215-
self._write_dictionary_to_yaml_file(self._dictionary, writer, file_name)
235+
self._write_collection_to_yaml_file(self._collection, writer, file_name)
216236
except JFileNotFoundException, fnfe:
217237
yaml_ex = exception_helper.create_yaml_exception('WLSDPLY-18010', file_name,
218238
fnfe.getLocalizedMessage(), error=fnfe)
@@ -228,20 +248,27 @@ def write_to_yaml_file(self, file_name):
228248

229249
self._logger.exiting(class_name=self._class_name, method_name=_method_name)
230250

231-
def _write_dictionary_to_yaml_file(self, dictionary, writer, file_name='<None>'):
251+
def write_to_stream(self, output_stream):
252+
self._write_collection_to_yaml_file(self._collection, OutputStreamWriter(output_stream))
253+
254+
def _write_collection_to_yaml_file(self, collection, writer, file_name='<None>'):
232255
"""
233-
Do the actual heavy lifting of converting a dictionary and writing it to the file.
234-
:param dictionary: the Python dictionary to convert
256+
Do the actual heavy lifting of converting a dictionary or document list and writing it to the file.
257+
:param collection: the Python dictionary or document list to convert
235258
:param writer: the java.io.Writer for the output file
236259
:param file_name: the file_name for the output file
237260
:raises: YamlException: if an error occurs while writing the output
238261
"""
239-
if dictionary is None:
262+
if collection is None:
240263
return
241264

242-
java_object = PythonToJava(dictionary).convert_to_java()
265+
java_object = PythonToJava(collection).convert_to_java()
243266
yaml_stream_translator = JYamlStreamTranslator(file_name, writer)
244-
yaml_stream_translator.dump(java_object)
267+
268+
if isinstance(collection, list):
269+
yaml_stream_translator.dumpDocuments(java_object)
270+
else:
271+
yaml_stream_translator.dump(java_object)
245272
return
246273

247274
def _close_writer(self, writer):

0 commit comments

Comments
 (0)