1 package org.daisy.pipeline.tts.impl;
2
3 import java.io.IOException;
4 import java.io.StringReader;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10
11 import javax.xml.transform.sax.SAXSource;
12
13 import net.sf.saxon.s9api.Processor;
14 import net.sf.saxon.s9api.SaxonApiException;
15 import net.sf.saxon.s9api.XdmNode;
16
17 import org.daisy.common.properties.Properties;
18 import org.daisy.pipeline.tts.config.ConfigReader;
19 import org.daisy.pipeline.tts.config.DynamicPropertiesExtension;
20 import org.daisy.pipeline.tts.TTSEngine;
21 import org.daisy.pipeline.tts.TTSRegistry;
22 import org.daisy.pipeline.tts.TTSService;
23 import org.daisy.pipeline.tts.TTSService.ServiceDisabledException;
24 import org.daisy.pipeline.webservice.restlet.AuthenticatedResource;
25 import org.daisy.pipeline.webservice.xml.XmlUtils;
26
27 import org.restlet.data.MediaType;
28 import org.restlet.data.Status;
29 import org.restlet.ext.xml.DomRepresentation;
30 import org.restlet.representation.Representation;
31 import org.restlet.resource.Get;
32 import org.restlet.resource.Post;
33
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import org.w3c.dom.Document;
38 import org.w3c.dom.Element;
39
40 import org.xml.sax.InputSource;
41
42 public class TTSEnginesResource extends AuthenticatedResource {
43
44 static final String TTS_REGISTRY_KEY = "tts-registry";
45
46 private static final Logger logger = LoggerFactory.getLogger(TTSEnginesResource.class.getName());
47 private static final Processor saxonProcessor = new Processor(false);
48
49 private TTSRegistry ttsRegistry;
50
51 @Override
52 public void doInit() {
53 super.doInit();
54 if (!isAuthenticated()) {
55 return;
56 }
57 ttsRegistry = (TTSRegistry)getContext().getAttributes().get(TTS_REGISTRY_KEY);
58 }
59
60
61
62
63
64
65 @Get("xml")
66 public Representation getResource() {
67 return getResource(null);
68 }
69
70
71
72
73 @Post("xml")
74 public Representation getResource(Representation ttsConfig) {
75 logRequest();
76 maybeEnableCORS();
77 if (!isAuthenticated()) {
78 setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
79 return null;
80 }
81 Collection<TTSService> ttsServices = ttsRegistry.getServices();
82 Map<String,String> properties; {
83 DynamicPropertiesExtension propsExt = new DynamicPropertiesExtension();
84 XdmNode configXML = null; {
85 if (ttsConfig != null) {
86 try {
87 configXML = saxonProcessor.newDocumentBuilder().build(
88 new SAXSource(new InputSource(new StringReader(ttsConfig.getText()))));
89 } catch (IOException|SaxonApiException e) {
90 logger.error("bad request:", e);
91 setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
92 return getErrorRepresentation(e.getMessage());
93 }
94 }
95 }
96 properties = Properties.getSnapshot();
97 new ConfigReader(saxonProcessor, configXML, properties, propsExt);
98 if (configXML != null)
99 logger.debug("TTS configuration XML:\n" + configXML);
100 Map<String,String> dynProperties = propsExt.getDynamicProperties();
101 if (dynProperties != null && !dynProperties.isEmpty()) {
102 properties = new HashMap<>(properties);
103 properties.putAll(dynProperties);
104 }
105 }
106 Document enginesDoc; {
107 enginesDoc = XmlUtils.createDom("tts-engines");
108 Element enginesElem = enginesDoc.getDocumentElement();
109 String baseURL = getRequest().getRootRef().toString();
110 enginesElem.setAttribute("href", baseURL + TTSEnginesWebServiceExtension.TTS_ENGINES_ROUTE);
111 for (TTSService s : ttsServices) {
112 Element engineElem = enginesDoc.createElementNS(XmlUtils.NS_PIPELINE_DATA, "tts-engine");
113 String name = s.getName();
114 engineElem.setAttribute("name", name);
115 engineElem.setAttribute("nicename", s.getDisplayName());
116 TTSEngine e = null;
117 Throwable error = null;
118 try {
119 e = s.newEngine(properties);
120 engineElem.setAttribute("status", "available");
121 } catch (ServiceDisabledException ex) {
122 logger.debug(name + " is disabled", ex);
123 error = ex;
124 engineElem.setAttribute("status", "disabled");
125 } catch (Throwable ex) {
126 logger.debug(name + " could not be activated", ex);
127 error = ex;
128 engineElem.setAttribute("status", "error");
129 }
130 if (e != null) {
131 engineElem.setAttribute("voices", baseURL + VoicesWebServiceExtension.VOICES_ROUTE + "?engine=" + name);
132 List<String> features = new ArrayList<>();
133 if (e.handlesSpeakingRate())
134 features.add("speech-rate");
135 if (e.handlesPronunciation())
136 features.add("phoneme");
137 if (!features.isEmpty())
138 engineElem.setAttribute("features", String.join(" ", features));
139 } else if (error != null) {
140
141
142 String shortMessage = error.getMessage();
143 error = error.getCause();
144 String detailedMessage = error != null ? error.getMessage() : null;
145 if (shortMessage.length() > 80) {
146
147
148
149 if (detailedMessage == null)
150 detailedMessage = shortMessage;
151 else {
152 detailedMessage = detailedMessage.trim();
153 shortMessage = shortMessage.trim();
154 if (detailedMessage.startsWith(shortMessage))
155 detailedMessage = detailedMessage.substring(shortMessage.length());
156 if (detailedMessage.startsWith(":")) {
157 detailedMessage = detailedMessage.substring(1);
158 detailedMessage = detailedMessage.trim();
159 }
160 if (detailedMessage.length() > 0) {
161 if (!shortMessage.matches(".*\\p{Punct}$"))
162 shortMessage += ":";
163 detailedMessage = shortMessage + " " + detailedMessage;
164 } else
165 detailedMessage = shortMessage;
166 }
167 shortMessage = "Could not connect to the service";
168 }
169 String message = shortMessage;
170 if (detailedMessage != null)
171 message += ("\n\n" + detailedMessage);
172 engineElem.setAttribute("message", message);
173 }
174 enginesElem.appendChild(engineElem);
175 }
176 }
177 DomRepresentation dom; {
178 try {
179 dom = new DomRepresentation(MediaType.APPLICATION_XML, enginesDoc);
180 setStatus(Status.SUCCESS_OK);
181 } catch (Exception e) {
182 setStatus(Status.SERVER_ERROR_INTERNAL);
183 return getErrorRepresentation(e.getMessage());
184 }
185 }
186 logResponse(dom);
187 return dom;
188 }
189 }