1   package org.daisy.pipeline.braille.common.calabash.impl;
2   
3   import java.util.ArrayList;
4   import java.util.Hashtable;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.NoSuchElementException;
9   
10  import com.google.common.collect.ImmutableMap;
11  import com.xmlcalabash.core.XProcException;
12  import com.xmlcalabash.core.XProcRuntime;
13  import com.xmlcalabash.io.ReadablePipe;
14  import com.xmlcalabash.io.WritablePipe;
15  import com.xmlcalabash.model.RuntimeValue;
16  import com.xmlcalabash.runtime.XAtomicStep;
17  
18  import net.sf.saxon.om.Item;
19  import net.sf.saxon.om.NodeInfo;
20  import net.sf.saxon.om.SequenceIterator;
21  import net.sf.saxon.s9api.QName;
22  import net.sf.saxon.s9api.SaxonApiException;
23  import net.sf.saxon.s9api.XdmNode;
24  import net.sf.saxon.value.StringValue;
25  
26  import org.daisy.common.saxon.SaxonHelper;
27  import org.daisy.common.saxon.SaxonInputValue;
28  import org.daisy.common.transform.SingleInSingleOutXMLTransformer;
29  import org.daisy.common.transform.TransformerException;
30  import org.daisy.common.xproc.calabash.XMLCalabashInputValue;
31  import org.daisy.common.xproc.calabash.XMLCalabashOutputValue;
32  import org.daisy.common.xproc.calabash.XMLCalabashParameterInputValue;
33  import org.daisy.common.xproc.calabash.XProcStep;
34  import org.daisy.common.xproc.calabash.XProcStepProvider;
35  import org.daisy.common.xproc.XProcMonitor;
36  import org.daisy.pipeline.braille.common.Transform;
37  import org.daisy.pipeline.braille.common.TransformProvider;
38  import org.daisy.pipeline.braille.common.Query.MutableQuery;
39  import org.daisy.pipeline.braille.common.Query;
40  import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
41  import static org.daisy.pipeline.braille.common.Query.util.query;
42  import static org.daisy.pipeline.braille.common.TransformProvider.util.dispatch;
43  import static org.daisy.pipeline.braille.common.TransformProvider.util.logSelect;
44  import org.daisy.pipeline.braille.common.XMLTransform;
45  
46  import org.osgi.service.component.annotations.Component;
47  import org.osgi.service.component.annotations.Reference;
48  import org.osgi.service.component.annotations.ReferenceCardinality;
49  import org.osgi.service.component.annotations.ReferencePolicy;
50  
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  public class PxTransformStep implements XProcStep {
55  	
56  	private final XProcMonitor monitor;
57  	private final Map<String,String> properties;
58  	private final TransformProvider<Transform> provider;
59  	private final XProcRuntime runtime;
60  	private final XAtomicStep step;
61  	private ReadablePipe source = null;
62  	private WritablePipe result = null;
63  	private final Hashtable<QName,RuntimeValue> params = new Hashtable<>();
64  	private final Hashtable<QName,RuntimeValue> options = new Hashtable<>();
65  	
66  	private static final QName _query = new QName("query");
67  	
68  	/**
69  	 * @param step is optional
70  	 */
71  	public PxTransformStep(XProcRuntime runtime,
72  	                       XAtomicStep step,
73  	                       XProcMonitor monitor,
74  	                       Map<String,String> properties,
75  	                       TransformProvider<Transform> provider) {
76  		if (runtime == null)
77  			throw new NullPointerException("runtime may not be null");
78  		else
79  			this.runtime = runtime;
80  		this.step = step;
81  		this.monitor = monitor;
82  		this.properties = properties;
83  		this.provider = provider;
84  	}
85  
86  	@Override
87  	public void setInput(String port, ReadablePipe pipe) {
88  		source = pipe;
89  	}
90  
91  	@Override
92  	public void setOutput(String port, WritablePipe pipe) {
93  		result = pipe;
94  	}
95  
96  	@Override
97  	public void reset() {
98  		source.resetReader();
99  		result.resetWriter();
100 	}
101 	
102 	@Override
103 	public void setParameter(QName name, RuntimeValue value) {
104 		params.put(name, value);
105 	}
106 	
107 	@Override
108 	public void setParameter(String port, QName name, RuntimeValue value) {
109 		setParameter(name, value);
110 	}
111 
112 	@Override
113 	public void setOption(QName name, RuntimeValue value) {
114 		options.put(name, value);
115 	}
116 
117 	@Override
118 	public void run() throws SaxonApiException {
119 		try {
120 			Query query = null; {
121 				RuntimeValue queryOption = options.get(_query);
122 				if (queryOption != null) {
123 					SequenceIterator iterator = queryOption.getValue().getUnderlyingValue().iterate();
124 					Item i;
125 					while ((i = iterator.next()) != null) {
126 						Query q; {
127 							if (i instanceof StringValue)
128 								q = query(((StringValue)i).getStringValue());
129 							else if (i instanceof NodeInfo)
130 								q = query(
131 									new SaxonInputValue(
132 										(Iterator<XdmNode>)SaxonHelper.iteratorFromSequence(i, XdmNode.class)
133 									).asXMLStreamReader());
134 							else
135 								throw new IllegalArgumentException();
136 						}
137 						if (query == null)
138 							query = q;
139 						else if (query instanceof MutableQuery)
140 							query = ((MutableQuery)query).addAll(q);
141 						else
142 							query = mutableQuery().addAll(query).addAll(q);
143 					}
144 				}
145 				if (query instanceof MutableQuery)
146 					query = ((MutableQuery)query).asImmutable();
147 			}
148 			SingleInSingleOutXMLTransformer xmlTransformer = null;
149 			try {
150 				for (Transform t : logSelect(query, provider, logger))
151 					if (t instanceof XProcStepProvider) {
152 						// if the transform is a XProcStepProvider, it is assumed to be declared as a p:pipeline
153 						xmlTransformer = SingleInSingleOutXMLTransformer.from(
154 							((XProcStepProvider)t).newStep(runtime, step, monitor, properties));
155 						break; }
156 					else if (t instanceof XMLTransform) {
157 						// fromXmlToXml() is assumed to have only a "source", a "parameters" and a "result" port
158 						// (i.e. the signature of px:transform without the "query" option)
159 						xmlTransformer = SingleInSingleOutXMLTransformer.from(
160 							((XMLTransform)t).fromXmlToXml());
161 						break; }}
162 			catch (NoSuchElementException e) {}
163 			if (xmlTransformer == null)
164 				throw new XProcException(step, "Could not find a Transform for query: " + query);
165 			xmlTransformer.transform(
166 				XMLCalabashInputValue.of(source),
167 				XMLCalabashOutputValue.of(result, runtime),
168 				XMLCalabashParameterInputValue.of(params)
169 			).run();
170 		} catch (Throwable e) {
171 			if (e instanceof TransformerException && e.getCause() instanceof XProcException)
172 				// assuming xmlTransformer is cx:eval based
173 				throw (XProcException)e.getCause();
174 			else
175 				throw XProcStep.raiseError(e, step);
176 		}
177 	}
178 	
179 	@Component(
180 		name = "px:transform",
181 		service = { XProcStepProvider.class },
182 		property = { "type:String={http://www.daisy.org/ns/pipeline/xproc}transform" }
183 	)
184 	public static class StepProvider implements XProcStepProvider {
185 		
186 		@Override
187 		public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
188 			return new PxTransformStep(runtime, step, monitor, properties, provider);
189 		}
190 		
191 		@Reference(
192 			name = "TransformProvider",
193 			unbind = "-",
194 			service = TransformProvider.class,
195 			cardinality = ReferenceCardinality.MULTIPLE,
196 			policy = ReferencePolicy.STATIC
197 		)
198 		@SuppressWarnings(
199 			"unchecked" // safe cast to TransformProvider<Transform>
200 		)
201 		public void bindTransformProvider(TransformProvider<?> provider) {
202 			providers.add((TransformProvider<Transform>)provider);
203 			logger.debug("Adding Transform provider: {}", provider);
204 		}
205 		
206 		private List<TransformProvider<Transform>> providers = new ArrayList<>();
207 		private TransformProvider<Transform> provider = dispatch(providers).withContext(logger);
208 		
209 	}
210 	
211 	private static final Logger logger = LoggerFactory.getLogger(PxTransformStep.class);
212 	
213 }