1
|
package eu.dnetlib.data.collective.transformation.engine.core;
|
2
|
|
3
|
import java.io.Reader;
|
4
|
import java.io.StringReader;
|
5
|
import java.io.StringWriter;
|
6
|
import java.util.ArrayList;
|
7
|
import java.util.HashMap;
|
8
|
import java.util.LinkedHashMap;
|
9
|
import java.util.List;
|
10
|
import java.util.Map;
|
11
|
import java.util.Properties;
|
12
|
|
13
|
import javax.xml.transform.OutputKeys;
|
14
|
import javax.xml.transform.Source;
|
15
|
import javax.xml.transform.Templates;
|
16
|
import javax.xml.transform.Transformer;
|
17
|
import javax.xml.transform.TransformerConfigurationException;
|
18
|
import javax.xml.transform.TransformerException;
|
19
|
import javax.xml.transform.TransformerFactory;
|
20
|
import javax.xml.transform.stream.StreamResult;
|
21
|
import javax.xml.transform.stream.StreamSource;
|
22
|
|
23
|
import net.sf.saxon.FeatureKeys;
|
24
|
import net.sf.saxon.instruct.TerminationException;
|
25
|
|
26
|
import org.apache.commons.logging.Log;
|
27
|
import org.apache.commons.logging.LogFactory;
|
28
|
import org.dom4j.Document;
|
29
|
import org.dom4j.DocumentException;
|
30
|
import org.dom4j.DocumentHelper;
|
31
|
import org.dom4j.Element;
|
32
|
import org.dom4j.Node;
|
33
|
import org.dom4j.io.SAXReader;
|
34
|
import org.springframework.core.io.Resource;
|
35
|
|
36
|
import eu.dnetlib.data.collective.transformation.TransformationException;
|
37
|
import eu.dnetlib.data.collective.transformation.core.schema.SchemaInspector;
|
38
|
import eu.dnetlib.data.collective.transformation.rulelanguage.RuleLanguageParser;
|
39
|
import eu.dnetlib.data.collective.transformation.rulelanguage.Rules;
|
40
|
import eu.dnetlib.data.collective.transformation.utils.NamespaceContextImpl;
|
41
|
|
42
|
/**
|
43
|
* @author jochen
|
44
|
*
|
45
|
*/
|
46
|
public class TransformationImpl implements
|
47
|
ITransformation {
|
48
|
|
49
|
private static final String rootElement = "record";
|
50
|
private final Log log = LogFactory.getLog(TransformationImpl.class);
|
51
|
private Document xslDoc;
|
52
|
private SAXReader reader = new SAXReader();
|
53
|
private Transformer transformer;
|
54
|
private Transformer transformerFailed;
|
55
|
protected RuleLanguageParser ruleLanguageParser;
|
56
|
private StylesheetBuilder stylesheetBuilder;
|
57
|
// cache static transformation results, valid for one transformation job
|
58
|
private Map<String, String> staticResults = new LinkedHashMap<String, String>();
|
59
|
private Map<String, String> jobConstantMap = new HashMap<String, String>();
|
60
|
|
61
|
@javax.annotation.Resource(name="template")
|
62
|
private Resource template;
|
63
|
|
64
|
private Resource schema;
|
65
|
|
66
|
private Source xsltSyntaxcheckFailed;
|
67
|
|
68
|
/**
|
69
|
* initializes the transformation with the underlying XSL-template
|
70
|
*/
|
71
|
public void init(){
|
72
|
try {
|
73
|
xslDoc = reader.read(template.getInputStream());
|
74
|
Resource xslResource = template.createRelative(XSLSyntaxcheckfailed);
|
75
|
String systemId = xslResource.getURL().toExternalForm();
|
76
|
xsltSyntaxcheckFailed = new StreamSource(xslResource.getInputStream(), systemId);
|
77
|
|
78
|
} catch (Throwable e) {
|
79
|
log.error("cannot initialize this transformation.", e);
|
80
|
throw new IllegalStateException(e);
|
81
|
}
|
82
|
|
83
|
}
|
84
|
|
85
|
public void addJobConstant(String aKey, String aValue){
|
86
|
this.jobConstantMap.put(aKey, aValue);
|
87
|
}
|
88
|
|
89
|
/**
|
90
|
* creates a new Transformer object using a stylesheet based on the transformation rules
|
91
|
*/
|
92
|
public void configureTransformation()throws TransformerConfigurationException{
|
93
|
final List<TransformerException> errorList = new ArrayList<TransformerException>();
|
94
|
|
95
|
javax.xml.transform.ErrorListener listener = new javax.xml.transform.ErrorListener() {
|
96
|
|
97
|
@Override
|
98
|
public void warning(TransformerException exception) throws TransformerException {
|
99
|
// TODO Auto-generated method stub
|
100
|
log.warn(exception);
|
101
|
|
102
|
}
|
103
|
|
104
|
@Override
|
105
|
public void fatalError(TransformerException exception) throws TransformerException {
|
106
|
// TODO Auto-generated method stub
|
107
|
errorList.add(exception);
|
108
|
log.fatal(exception);
|
109
|
throw exception;
|
110
|
}
|
111
|
|
112
|
@Override
|
113
|
public void error(TransformerException exception) throws TransformerException {
|
114
|
// TODO Auto-generated method stub
|
115
|
errorList.add(exception);
|
116
|
log.error(exception);
|
117
|
throw exception;
|
118
|
}
|
119
|
};
|
120
|
|
121
|
TransformerFactory factory = TransformerFactory.newInstance();
|
122
|
factory.setAttribute(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, Boolean.TRUE);
|
123
|
factory.setErrorListener(listener);
|
124
|
Templates templates = null;
|
125
|
try{
|
126
|
if (this.ruleLanguageParser.isXslStylesheet()){
|
127
|
templates = factory.newTemplates(new StreamSource(new StringReader(ruleLanguageParser.getXslStylesheet())));
|
128
|
}else{
|
129
|
templates = factory.newTemplates(new StreamSource(createStylesheet()));
|
130
|
}
|
131
|
|
132
|
transformer = templates.newTransformer();
|
133
|
//((net.sf.saxon.Controller)transformer).setMessageEmitter(mw);
|
134
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
135
|
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
|
136
|
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
137
|
|
138
|
Templates templateFailed = factory.newTemplates(xsltSyntaxcheckFailed);
|
139
|
transformerFailed = templateFailed.newTransformer();
|
140
|
}catch(TransformerConfigurationException e){
|
141
|
if (!errorList.isEmpty()) {
|
142
|
System.out.println(errorList.get(0).getMessageAndLocation()); // todo it seems the location information is not yet correct
|
143
|
log.error(errorList.get(0).getMessageAndLocation());
|
144
|
throw new TransformerConfigurationException(errorList.get(0).getMessageAndLocation());
|
145
|
}else{
|
146
|
throw e;
|
147
|
}
|
148
|
}
|
149
|
|
150
|
//((net.sf.saxon.Controller)transformerFailed).setMessageEmitter(mw);
|
151
|
}
|
152
|
|
153
|
/* (non-Javadoc)
|
154
|
* @see eu.dnetlib.data.collective.transformation.engine.core.ITransformation#transformRecord(java.lang.String, int)
|
155
|
*/
|
156
|
public String transformRecord(String record, int index)throws TerminationException, TransformationException{
|
157
|
try {
|
158
|
StreamSource s = new StreamSource(new StringReader(record));
|
159
|
StringWriter writer = new StringWriter();
|
160
|
StreamResult r = new StreamResult(writer);
|
161
|
transformer.setParameter("index", index);
|
162
|
transformer.transform(s , r);
|
163
|
return writer.toString();
|
164
|
}catch (TerminationException e) {
|
165
|
log.debug(e.getLocalizedMessage());
|
166
|
throw e;
|
167
|
} catch (TransformerException e) {
|
168
|
log.error(e);
|
169
|
throw new TransformationException(e);
|
170
|
}
|
171
|
}
|
172
|
|
173
|
public String transformRecord(String record, Map<String, String> parameters) throws TerminationException, TransformationException{
|
174
|
try {
|
175
|
StreamSource s = new StreamSource(new StringReader(record));
|
176
|
StringWriter writer = new StringWriter();
|
177
|
StreamResult r = new StreamResult(writer);
|
178
|
for (String key: parameters.keySet()){
|
179
|
transformer.setParameter(key, parameters.get(key));
|
180
|
}
|
181
|
transformer.transform(s , r);
|
182
|
return writer.toString();
|
183
|
}catch (TerminationException e){
|
184
|
log.debug(e.getLocalizedMessage());
|
185
|
throw e;
|
186
|
} catch (TransformerException e) {
|
187
|
log.error(e);
|
188
|
throw new TransformationException(e);
|
189
|
}
|
190
|
}
|
191
|
|
192
|
public String transformRecord(String record, String stylesheetName) throws TransformationException{
|
193
|
if (!stylesheetName.equals(XSLSyntaxcheckfailed))
|
194
|
throw new IllegalArgumentException("in TransformationImpl: stylesheetname " + stylesheetName + " is unsupported!" );
|
195
|
try{
|
196
|
StreamSource s = new StreamSource(new StringReader(record));
|
197
|
StringWriter w = new StringWriter();
|
198
|
StreamResult r = new StreamResult(w);
|
199
|
transformerFailed.transform(s, r);
|
200
|
return w.toString();
|
201
|
}catch (TransformerException e){
|
202
|
log.error(e);
|
203
|
throw new TransformationException(e);
|
204
|
}
|
205
|
}
|
206
|
|
207
|
public String dumpStylesheet(){
|
208
|
return xslDoc.asXML();
|
209
|
|
210
|
// StringWriter writer = new StringWriter();
|
211
|
// try {
|
212
|
// Transformer tXsl = transformer; //.newTransformer();
|
213
|
// tXsl.setOutputProperty(OutputKeys.INDENT, "yes");
|
214
|
// tXsl.setOutputProperty(OutputKeys.METHOD, "xml");
|
215
|
// tXsl.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
216
|
//
|
217
|
// StreamResult r = new StreamResult(writer);
|
218
|
// Source s = new StreamSource(new StringReader(xslDoc.asXML()));
|
219
|
// tXsl.transform(s, r);
|
220
|
// } catch (TransformerException e) {
|
221
|
// // TODO Auto-generated catch block
|
222
|
// e.printStackTrace();
|
223
|
// }
|
224
|
// return writer.toString();
|
225
|
}
|
226
|
|
227
|
|
228
|
/**
|
229
|
* sets the XSL template
|
230
|
* @param template - resource to access the XSL template
|
231
|
*/
|
232
|
public void setTemplate(Resource template) {
|
233
|
this.template = template;
|
234
|
}
|
235
|
|
236
|
/**
|
237
|
* @return the resource to access the XSL template
|
238
|
*/
|
239
|
public Resource getTemplate() {
|
240
|
return template;
|
241
|
}
|
242
|
|
243
|
public void setRuleLanguageParser(RuleLanguageParser ruleLanguageParser) {
|
244
|
this.ruleLanguageParser = ruleLanguageParser;
|
245
|
}
|
246
|
|
247
|
public RuleLanguageParser getRuleLanguageParser() {
|
248
|
return ruleLanguageParser;
|
249
|
}
|
250
|
|
251
|
/**
|
252
|
* @param stylesheetBuilder the stylesheetBuilder to set
|
253
|
*/
|
254
|
public void setStylesheetBuilder(StylesheetBuilder stylesheetBuilder) {
|
255
|
this.stylesheetBuilder = stylesheetBuilder;
|
256
|
}
|
257
|
|
258
|
/**
|
259
|
* @return the stylesheetBuilder
|
260
|
*/
|
261
|
public StylesheetBuilder getStylesheetBuilder() {
|
262
|
return stylesheetBuilder;
|
263
|
}
|
264
|
|
265
|
/**
|
266
|
* @return the transformation rules as String object
|
267
|
*/
|
268
|
protected String getTransformationRules(){
|
269
|
// add job-properties to the rules as variables
|
270
|
for (String key: jobConstantMap.keySet()){
|
271
|
Rules r = new Rules();
|
272
|
r.setVariable(key);
|
273
|
r.setConstant("'" + jobConstantMap.get(key) + "'");
|
274
|
ruleLanguageParser.getVariableMappingRules().put(JOBCONST_DATASINKID, r);
|
275
|
}
|
276
|
if (this.stylesheetBuilder == null){
|
277
|
// create DMF compliant stylesheet builder
|
278
|
this.stylesheetBuilder = new StylesheetBuilder();
|
279
|
this.stylesheetBuilder.setRuleLanguageParser(this.ruleLanguageParser);
|
280
|
NamespaceContextImpl namespaceContext = new NamespaceContextImpl();
|
281
|
for (String prefix: ruleLanguageParser.getNamespaceDeclarations().keySet()){
|
282
|
namespaceContext.addNamespace(prefix, ruleLanguageParser.getNamespaceDeclarations().get(prefix));
|
283
|
}
|
284
|
SchemaInspector inspector = new SchemaInspector();
|
285
|
try {
|
286
|
inspector.inspect(this.schema.getURL(), rootElement);
|
287
|
} catch (Exception e) {
|
288
|
throw new IllegalStateException(e);
|
289
|
}
|
290
|
this.stylesheetBuilder.setNamespaceContext(namespaceContext);
|
291
|
this.stylesheetBuilder.setSchemaInspector(inspector);
|
292
|
}
|
293
|
return this.stylesheetBuilder.createTemplate();
|
294
|
}
|
295
|
|
296
|
/**
|
297
|
* creates a stylesheet from transformation rules;
|
298
|
* <p>don't call this method multiple times, unless transformation configuration changes, then re-init and configure transformation</p>
|
299
|
* @return the stylesheet
|
300
|
*/
|
301
|
private Reader createStylesheet(){
|
302
|
try {
|
303
|
Document rulesDoc = DocumentHelper.parseText(getTransformationRules());
|
304
|
for(String key: this.ruleLanguageParser.getNamespaceDeclarations().keySet()){
|
305
|
xslDoc.getRootElement().addNamespace(key, this.ruleLanguageParser.getNamespaceDeclarations().get(key));
|
306
|
}
|
307
|
@SuppressWarnings("unchecked")
|
308
|
List<Node> nodes = rulesDoc.getRootElement().selectNodes("//xsl:template");
|
309
|
|
310
|
@SuppressWarnings("unchecked")
|
311
|
List<Node> varNodes = rulesDoc.getRootElement().selectNodes("/templateroot/xsl:param");
|
312
|
for (Node node: varNodes){
|
313
|
xslDoc.getRootElement().add( ((Element)node).detach() );
|
314
|
}
|
315
|
|
316
|
// xslDoc.getRootElement().add(rulesDoc.getRootElement().selectSingleNode("//xsl:param[@name='var1']").detach());
|
317
|
for (Node node: nodes){
|
318
|
xslDoc.getRootElement().add( ((Element)node).detach() ); // (rulesDoc.getRootElement().aget);
|
319
|
}
|
320
|
} catch (DocumentException e) {
|
321
|
log.error("error in creating stylesheet: " + e);
|
322
|
throw new IllegalStateException(e);
|
323
|
}
|
324
|
return new StringReader(xslDoc.asXML());
|
325
|
}
|
326
|
|
327
|
/**
|
328
|
* @param schema the schema to set
|
329
|
*/
|
330
|
public void setSchema(Resource schema) {
|
331
|
this.schema = schema;
|
332
|
}
|
333
|
|
334
|
/**
|
335
|
* @return the schema
|
336
|
*/
|
337
|
public Resource getSchema() {
|
338
|
return schema;
|
339
|
}
|
340
|
|
341
|
@Override
|
342
|
public Map<String, String> getStaticTransformationResults() {
|
343
|
return this.staticResults;
|
344
|
}
|
345
|
|
346
|
@Override
|
347
|
public Map<String, String> getJobProperties() {
|
348
|
// TODO Auto-generated method stub
|
349
|
return this.jobConstantMap;
|
350
|
}
|
351
|
|
352
|
@Override
|
353
|
public Properties getLogInformation() {
|
354
|
// TODO Auto-generated method stub
|
355
|
return null;
|
356
|
}
|
357
|
|
358
|
}
|