source: FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/utils/SimpleResourceInfoInventoryParser.java @ 5477

Last change on this file since 5477 was 5477, checked in by margaretha@ids-mannheim.de, 10 years ago

Added endpoint description,
updated SimpleResourceInfoInventory? according to the new format.

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1package eu.clarin.sru.server.fcs.utils;
2
3import java.io.IOException;
4import java.net.URL;
5import java.util.ArrayList;
6import java.util.Arrays;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.Iterator;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
13import java.util.Set;
14
15import javax.xml.XMLConstants;
16import javax.xml.namespace.NamespaceContext;
17import javax.xml.parsers.DocumentBuilder;
18import javax.xml.parsers.DocumentBuilderFactory;
19import javax.xml.parsers.ParserConfigurationException;
20import javax.xml.xpath.XPath;
21import javax.xml.xpath.XPathConstants;
22import javax.xml.xpath.XPathExpression;
23import javax.xml.xpath.XPathExpressionException;
24import javax.xml.xpath.XPathFactory;
25
26import org.slf4j.Logger;
27import org.slf4j.LoggerFactory;
28import org.w3c.dom.Document;
29import org.w3c.dom.Element;
30import org.w3c.dom.Node;
31import org.w3c.dom.NodeList;
32import org.xml.sax.SAXException;
33
34import eu.clarin.sru.server.SRUConfigException;
35import eu.clarin.sru.server.fcs.ResourceInfoInventory;
36import eu.clarin.sru.server.fcs.ResourceInfo;
37
38
39/**
40 * A parser, that parses an XML file and produces a static list of resource info
41 * records. The resulting list can be used to construct a
42 * {@link SimpleResourceInfoInventory} instance.
43 *
44 * @see ResourceInfo
45 * @see SimpleResourceInfoInventory
46 */
47public class SimpleResourceInfoInventoryParser {
48    private static final String NS = "http://clarin.eu/fcs/endpoint-description";
49    private static final String LANG_EN = "en";
50    private static final Logger logger =
51            LoggerFactory.getLogger(SimpleResourceInfoInventoryParser.class);
52
53
54    /**
55     * Parse an XML file and return a static list of resource info records.
56     *
57     * @param url
58     *            the URI pointing to the file to be parsed
59     * @return a list of resource info records represented as
60     *         {@link ResourceInfo} instances
61     * @throws SRUConfigException
62     *             if an error occurred
63     */
64    public static ResourceInfoInventory parse(URL url) throws SRUConfigException {
65        if (url == null) {
66            throw new NullPointerException("url == null");
67        }
68
69        logger.debug("parsing resource-info from: {}", url);
70
71        final Set<String> ids = new HashSet<String>();
72        try {
73            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
74            dbf.setNamespaceAware(true);
75            dbf.setCoalescing(true);
76            DocumentBuilder db = dbf.newDocumentBuilder();
77            Document doc = db.parse(url.openStream());
78
79            XPathFactory factory = XPathFactory.newInstance();
80            XPath xpath = factory.newXPath();
81            xpath.setNamespaceContext(new NamespaceContext() {
82                @Override
83                public Iterator<?> getPrefixes(String namespaceURI) {
84                    throw new UnsupportedOperationException();
85                }
86
87                @Override
88                public String getPrefix(String namespaceURI) {
89                    throw new UnsupportedOperationException();
90                }
91
92                @Override
93                public String getNamespaceURI(String prefix) {
94                    if (prefix == null) {
95                        throw new NullPointerException("prefix == null");
96                    }
97                    if (prefix.equals("ri")) {
98                        return NS;
99                    } else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
100                        return XMLConstants.XML_NS_URI;
101                    } else {
102                        return XMLConstants.NULL_NS_URI;
103                    }
104                }
105            });
106            XPathExpression expression =
107                    xpath.compile("/ri:Resources/ri:Resource");
108            NodeList list =
109                    (NodeList) expression.evaluate(doc, XPathConstants.NODESET);
110
111            List<ResourceInfo> entries = parseResourceInfo(xpath, list, ids);
112            return new SimpleResourceInfoInventory(entries, false);
113        } catch (ParserConfigurationException e) {
114            e.printStackTrace();
115        } catch (SAXException e) {
116            e.printStackTrace();
117        } catch (IOException e) {
118            e.printStackTrace();
119        } catch (XPathExpressionException e) {
120            e.printStackTrace();
121        }
122        return null;
123    }
124
125
126    private static List<ResourceInfo> parseResourceInfo(XPath xpath,
127            NodeList nodes, Set<String> ids) throws SRUConfigException,
128            XPathExpressionException {
129        logger.debug("parsing 'ResourceInfo' ({} nodes) ...",
130                nodes.getLength());
131
132        List<ResourceInfo> ris = null;
133        for (int k = 0; k < nodes.getLength(); k++) {
134            final Element node = (Element) nodes.item(k);
135            String pid = null;
136            int resourceCount = -1;
137            Map<String, String> titles = null;
138            Map<String, String> descrs = null;
139            String link = null;
140            List<String> langs = null;
141            String[] availableDataViews = null;
142            List<ResourceInfo> sub = null;
143
144            pid = node.getAttribute("pid");
145            if (pid != null) {
146                pid = pid.trim();
147                if (pid.isEmpty()) {
148                    pid = null;
149                }
150            }
151            if (pid == null) {
152                throw new SRUConfigException("Element <ResourceInfo> " +
153                        "must carry a proper 'pid' attribute");
154            }
155            if (ids.contains(pid)) {
156                throw new SRUConfigException("Another element <ResourceInfo> " +
157                        "with pid '" + pid + "' already exists");
158            }
159            ids.add(pid);
160
161            XPathExpression x1 = xpath.compile("ri:Title");
162            NodeList l1 = (NodeList) x1.evaluate(node, XPathConstants.NODESET);
163            if (l1 != null) {
164                for (int i = 0; i < l1.getLength(); i++) {
165                    final Element n = (Element) l1.item(i);
166
167                    final String lang = getLangAttribute(n);
168                    if (lang == null) {
169                        throw new SRUConfigException("Element <Title> must " +
170                                "carry a proper 'xml:lang' attribute");
171                    }
172
173                    final String title = cleanString(n.getTextContent());
174                    if (title == null) {
175                        throw new SRUConfigException("Element <Title> must " +
176                                "carry a non-empty 'xml:lang' attribute");
177                    }
178
179                    if (titles == null) {
180                        titles = new HashMap<String, String>();
181                    }
182                    if (titles.containsKey(lang)) {
183                        logger.warn("title with language '{}' already exists",
184                                lang);
185                    } else {
186                        logger.debug("title: '{}' '{}'", lang, title);
187                        titles.put(lang, title);
188                    }
189                }
190                if ((titles != null) && !titles.containsKey(LANG_EN)) {
191                    throw new SRUConfigException(
192                            "A <Title> with language 'en' is mandatory");
193                }
194            }
195            XPathExpression x2 = xpath.compile("ri:Description");
196            NodeList l2 = (NodeList) x2.evaluate(node, XPathConstants.NODESET);
197            if (l2 != null) {
198                for (int i = 0; i < l2.getLength(); i++) {
199                    Element n = (Element) l2.item(i);
200
201                    String lang = getLangAttribute(n);
202                    if (lang == null) {
203                        throw new SRUConfigException("Element <Description> " +
204                                "must carry a proper 'xml:lang' attribute");
205
206                    }
207                    String desc = cleanString(n.getTextContent());
208
209                    if (descrs == null) {
210                        descrs = new HashMap<String, String>();
211                    }
212
213                    if (descrs.containsKey(lang)) {
214                        logger.warn("description with language '{}' "
215                                + "already exists", lang);
216                    } else {
217                        logger.debug("description: '{}' '{}'", lang, desc);
218                        descrs.put(lang, desc);
219                    }
220                }
221                if ((descrs != null) && !descrs.containsKey(LANG_EN)) {
222                    throw new SRUConfigException(
223                            "A <Description> with language 'en' is mandatory");
224                }
225            }
226
227            XPathExpression x3 = xpath.compile("ri:LandingPageURI");
228            NodeList l3 = (NodeList) x3.evaluate(node, XPathConstants.NODESET);
229            if (l3 != null) {
230                for (int i = 0; i < l3.getLength(); i++) {
231                    Element n = (Element) l3.item(i);
232                    link = cleanString(n.getTextContent());
233                    logger.debug("link: \"{}\"", n.getTextContent());
234                }
235            }
236
237            XPathExpression x4 = xpath.compile("ri:Languages/ri:Language");
238            NodeList l4 = (NodeList) x4.evaluate(node, XPathConstants.NODESET);
239            if (l4 != null) {
240                for (int i = 0; i < l4.getLength(); i++) {
241                    Element n = (Element) l4.item(i);
242
243                    String s = n.getTextContent();
244                    if (s != null) {
245                        s = s.trim();
246                        if (s.isEmpty()) {
247                            s = null;
248                        }
249                    }
250
251                    /*
252                     * enforce three letter codes
253                     */
254                    if ((s == null) || (s.length() != 3)) {
255                        throw new SRUConfigException("Element <Language> " +
256                                "must use ISO-632-3 three letter " +
257                                "language codes");
258                    }
259
260                    if (langs == null) {
261                        langs = new ArrayList<String>();
262                    }
263                    logger.debug("language: '{}'", n.getTextContent());
264                    langs.add(s);
265                }
266            }
267           
268            XPathExpression x6 = xpath.compile("ri:AvailableDataViews/@ref");
269            String ref = (String) x6.evaluate(node, XPathConstants.STRING);
270           
271            if (ref == null || ref.isEmpty()){
272                throw new SRUConfigException("Element <AvailableDataViews> " +
273                        "must have a non-empty attribute ref.");
274            }
275           
276            availableDataViews = ref.split("\\s+");
277           
278           
279            XPathExpression x5 =
280                    xpath.compile("ri:Resources/ri:Resource");
281            NodeList l5 = (NodeList) x5.evaluate(node, XPathConstants.NODESET);
282            if ((l5 != null) && (l5.getLength() > 0)) {
283                sub = parseResourceInfo(xpath, l5, ids);
284            }
285
286            if (ris == null) {
287                ris = new LinkedList<ResourceInfo>();
288            }
289            ris.add(new ResourceInfo(pid, resourceCount, titles, descrs, link,
290                    langs, Arrays.asList(availableDataViews),sub));
291        }
292        return ris;
293    }
294
295
296    private static String getLangAttribute(Element el) {
297        String lang = el.getAttributeNS(XMLConstants.XML_NS_URI, "lang");
298        if (lang != null) {
299            lang = lang.trim();
300            if (!lang.isEmpty()) {
301                return lang;
302            }
303        }
304        return null;
305    }
306
307
308    private static String cleanString(String s) {
309        if (s != null) {
310            s = s.trim();
311            if (!s.isEmpty()) {
312                StringBuilder sb = new StringBuilder();
313                for (String z : s.split("\\s*\\n+\\s*")) {
314                    z = z.trim();
315                    if (!z.isEmpty()) {
316                        if (sb.length() > 0) {
317                            sb.append(' ');
318                        }
319                        sb.append(z);
320                    }
321                }
322                if (sb.length() > 0) {
323                    return sb.toString();
324                }
325            }
326        }
327        return null;
328    }
329
330} // class SimpleResourceInfoInventoryParser
Note: See TracBrowser for help on using the repository browser.