source: FCSSimpleEndpoint/tags/FCSSimpleEndpoint-1.1.0/src/main/java/eu/clarin/sru/server/fcs/SimpleEndpointSearchEngineBase.java @ 2793

Last change on this file since 2793 was 2793, checked in by oschonef, 11 years ago
  • tag version 1.1.0
  • Property svn:eol-style set to native
File size: 18.4 KB
Line 
1package eu.clarin.sru.server.fcs;
2
3import java.util.Collections;
4import java.util.List;
5import java.util.Map;
6
7import javax.servlet.ServletContext;
8import javax.xml.XMLConstants;
9import javax.xml.stream.XMLStreamException;
10import javax.xml.stream.XMLStreamWriter;
11
12import org.slf4j.Logger;
13import org.slf4j.LoggerFactory;
14import org.z3950.zing.cql.CQLNode;
15import org.z3950.zing.cql.CQLRelation;
16import org.z3950.zing.cql.CQLTermNode;
17import org.z3950.zing.cql.Modifier;
18
19import eu.clarin.sru.server.SRUConfigException;
20import eu.clarin.sru.server.SRUConstants;
21import eu.clarin.sru.server.SRUDiagnosticList;
22import eu.clarin.sru.server.SRUException;
23import eu.clarin.sru.server.SRUExplainResult;
24import eu.clarin.sru.server.SRURequest;
25import eu.clarin.sru.server.SRUScanResultSet;
26import eu.clarin.sru.server.SRUSearchEngine;
27import eu.clarin.sru.server.SRUServerConfig;
28import eu.clarin.sru.server.utils.SRUSearchEngineBase;
29
30
31/**
32 * A base class for implementing a simple search engine to be used as a CLARIN
33 * FCS endpoint.
34 *
35 */
36public abstract class SimpleEndpointSearchEngineBase extends
37        SRUSearchEngineBase {
38    private static final String FCS_RESOURCE_INFO_NS =
39            "http://clarin.eu/fcs/1.0/resource-info";
40    private static final String X_CMD_RESOURCE_INFO = "x-cmd-resource-info";
41    private static final String FCS_SCAN_INDEX_FCS_RESOURCE = "fcs.resource";
42    private static final String FCS_SCAN_INDEX_CQL_SERVERCHOICE = "cql.serverChoice";
43    private static final String FCS_SCAN_SUPPORTED_RELATION_CQL_1_1 = "scr";
44    private static final String FCS_SCAN_SUPPORTED_RELATION_CQL_1_2 = "=";
45    private static final String FCS_SUPPORTED_RELATION_EXACT = "exact";
46    private static final Logger logger =
47            LoggerFactory.getLogger(SimpleEndpointSearchEngineBase.class);
48    protected ResourceInfoInventory resourceInfoInventory;
49
50
51    /**
52     * This method should not be overridden. Perform your custom initialization
53     * in the {@link #doInit(ServletContext, SRUServerConfig, Map)} method
54     * Instead.
55     *
56     * @see #doInit(ServletContext, SRUServerConfig, Map)
57     */
58    @Override
59    public final void init(ServletContext context, SRUServerConfig config,
60            Map<String, String> params) throws SRUConfigException {
61        logger.debug("initializing");
62        super.init(context, config, params);
63
64        logger.debug("initializing search engine implementation");
65        doInit(context, config, params);
66
67        logger.debug("initizalizing resource info inventory");
68        this.resourceInfoInventory = createResourceInfoInventory(context, config, params);
69        if (this.resourceInfoInventory == null) {
70            logger.error("ClarinFCSSearchEngineBase implementation error: " +
71                    "initResourceCatalog() returned null");
72            throw new SRUConfigException("initResourceCatalog() returned no " +
73                    "valid implementation of a ResourceCatalog");
74        }
75    }
76
77
78    /**
79     * This method should not be overridden. Perform you custom cleanup in the
80     * {@link #doDestroy()} method.
81     *
82     * @see #doDestroy()
83     */
84    @Override
85    public final void destroy() {
86        logger.debug("performing cleanup of resource info inventory");
87        resourceInfoInventory.destroy();
88        logger.debug("performing cleanup of search engine");
89        doDestroy();
90        super.destroy();
91    }
92
93
94    @Override
95    public final SRUExplainResult explain(SRUServerConfig config,
96            SRURequest request, SRUDiagnosticList diagnostics)
97            throws SRUException {
98        final boolean provideResourceInfo =
99                parseBoolean(request.getExtraRequestData(X_CMD_RESOURCE_INFO));
100        if (provideResourceInfo) {
101            final List<ResourceInfo> resourceInfoList =
102                    resourceInfoInventory.getResourceInfoList(
103                            ResourceInfoInventory.PID_ROOT);
104            return new SRUExplainResult(diagnostics) {
105
106                @Override
107                public boolean hasExtraResponseData() {
108                    return provideResourceInfo;
109                }
110
111                @Override
112                public void writeExtraResponseData(XMLStreamWriter writer)
113                        throws XMLStreamException {
114                    writeFullResourceInfo(writer, null, resourceInfoList);
115                }
116            };
117        } else {
118            return null;
119        }
120    }
121
122
123    /**
124     * Handle a <em>scan</em> operation. This implementation provides support to
125     * CLARIN FCS resource enumeration. If you want to provide custom scan
126     * behavior for a different index, override the
127     * {@link #doScan(SRUServerConfig, SRURequest, SRUDiagnosticList)} method.
128     *
129     * @see #doScan(SRUServerConfig, SRURequest, SRUDiagnosticList)
130     */
131    @Override
132    public final SRUScanResultSet scan(SRUServerConfig config,
133            SRURequest request, SRUDiagnosticList diagnostics)
134            throws SRUException {
135        /*
136         * Check if we got a scan on fcs.resource. If yes, handle it
137         * accordingly, otherwise delegate to user-provided implementation.
138         */
139        final List<ResourceInfo> result =
140                translateFcsScanResource(request.getScanClause());
141        if (result != null) {
142            final boolean provideResourceInfo = parseBoolean(
143                    request.getExtraRequestData(X_CMD_RESOURCE_INFO));
144            return new SRUScanResultSet(diagnostics) {
145                private int idx = -1;
146
147                @Override
148                public boolean nextTerm() {
149                    return (result != null) && (++idx < result.size());
150                }
151
152
153                @Override
154                public String getValue() {
155                    return result.get(idx).getPid();
156                }
157
158
159                @Override
160                public int getNumberOfRecords() {
161                    return result.get(idx).getResourceCount();
162                }
163
164
165                @Override
166                public String getDisplayTerm() {
167                    return result.get(idx).getTitle("en");
168                }
169
170
171                @Override
172                public WhereInList getWhereInList() {
173                    return null;
174                }
175
176
177                @Override
178                public boolean hasExtraTermData() {
179                    return provideResourceInfo;
180                }
181
182
183                @Override
184                public void writeExtraTermData(XMLStreamWriter writer)
185                        throws XMLStreamException {
186                    if (provideResourceInfo) {
187                        writeResourceInfo(writer, null, result.get(idx));
188                    }
189                }
190            };
191        } else {
192            return doScan(config, request, diagnostics);
193        }
194    }
195
196
197
198    /**
199     * Create the resource info inventory to be used with this endpoint.
200     * Implement this method to provide an implementation of a
201     * {@link ResourceInfoInventory} that is tailored towards your environment
202     * and needs.
203     *
204     * @param context
205     *            the {@link ServletContext} for the Servlet
206     * @param config
207     *            the {@link SRUServerConfig} object for this search engine
208     * @param params
209     *            additional parameters gathered from the Servlet configuration
210     *            and Servlet context.
211     * @return an instance of a {@link ResourceInfoInventory} used by this
212     *         search engine
213     * @throws SRUConfigException
214     *             if an error occurred
215     */
216    protected abstract ResourceInfoInventory createResourceInfoInventory(
217            ServletContext context, SRUServerConfig config,
218            Map<String, String> params) throws SRUConfigException;
219
220
221    /**
222     * Initialize the search engine. This initialization should be tailed
223     * towards your environment and needs.
224     *
225     * @param context
226     *            the {@link ServletContext} for the Servlet
227     * @param config
228     *            the {@link SRUServerConfig} object for this search engine
229     * @param params
230     *            additional parameters gathered from the Servlet configuration
231     *            and Servlet context.
232     * @throws SRUConfigException
233     *             if an error occurred
234     */
235    protected abstract void doInit(ServletContext context,
236            SRUServerConfig config, Map<String, String> params)
237            throws SRUConfigException;
238
239
240    /**
241     * Destroy the search engine. Override this method for any cleanup the
242     * search engine needs to perform upon termination.
243     */
244    protected void doDestroy() {
245    }
246
247
248    /**
249     * Handle a <em>explain</em> operation. The default implementation is a
250     * no-op. Override this method, if you want to provide a custom behavior.
251     *
252     * @see SRUSearchEngine#explain(SRUServerConfig, SRURequest,
253     *      SRUDiagnosticList)
254     */
255    protected SRUScanResultSet doScan(SRUServerConfig config,
256            SRURequest request, SRUDiagnosticList diagnostics)
257            throws SRUException {
258        final CQLNode scanClause = request.getScanClause();
259        if (scanClause instanceof CQLTermNode) {
260            final CQLTermNode root = (CQLTermNode) scanClause;
261            final String index = root.getIndex();
262            throw new SRUException(SRUConstants.SRU_UNSUPPORTED_INDEX, index,
263                    "scan operation on index '" + index + "' is not supported");
264        } else {
265            throw new SRUException(SRUConstants.SRU_QUERY_FEATURE_UNSUPPORTED,
266                    "Scan clause too complex.");
267        }
268    }
269
270
271    /**
272     * Convince method for parsing a string to boolean. Values <code>1</code>,
273     * <code>true</code>, <code>yes</code> yield a <em>true</em> boolean value
274     * as a result, all others (including <code>null</code>) a <em>false</em>
275     * boolean value.
276     *
277     * @param value
278     *            the string to parse
279     * @return <code>true</code> if the supplied string was considered something
280     *         representing a <em>true</em> boolean value, <code>false</code>
281     *         otherwise
282     */
283    protected static boolean parseBoolean(String value) {
284        if (value != null) {
285            return value.equals("1") || Boolean.parseBoolean(value);
286        }
287        return false;
288    }
289
290
291    private List<ResourceInfo> translateFcsScanResource(CQLNode scanClause)
292            throws SRUException {
293        if (scanClause instanceof CQLTermNode) {
294            final CQLTermNode root = (CQLTermNode) scanClause;
295            logger.debug("index = '{}', relation = '{}', term = '{}'",
296                    new Object[] { root.getIndex(),
297                            root.getRelation().getBase(), root.getTerm() });
298
299            String index = root.getIndex();
300            if (FCS_SCAN_INDEX_CQL_SERVERCHOICE.equals(index) &&
301                    FCS_SCAN_INDEX_FCS_RESOURCE.equals(root.getTerm())) {
302                throw new SRUException(SRUConstants.SRU_UNSUPPORTED_INDEX,
303                        "scan operation with 'scanClause' with value " +
304                        "'fcs.resource' is deprecated within CLARIN-FCS");
305            }
306            if (!(FCS_SCAN_INDEX_FCS_RESOURCE.equals(index))) {
307                logger.debug("got scan operation on index '{}', bailing ...",
308                        index);
309                return null;
310            }
311
312
313            // only allow "=" relation without any modifiers
314            final CQLRelation relationNode = root.getRelation();
315            String relation = relationNode.getBase();
316            if (!(FCS_SCAN_SUPPORTED_RELATION_CQL_1_1.equals(relation) ||
317                    FCS_SCAN_SUPPORTED_RELATION_CQL_1_2.equals(relation) ||
318                    FCS_SUPPORTED_RELATION_EXACT.equals(relation))) {
319                throw new SRUException(SRUConstants.SRU_UNSUPPORTED_RELATION,
320                        relationNode.getBase(), "Relation \"" +
321                                relationNode.getBase() +
322                                "\" is not supported in scan operation.");
323            }
324            final List<Modifier> modifiers = relationNode.getModifiers();
325            if ((modifiers != null) && !modifiers.isEmpty()) {
326                Modifier modifier = modifiers.get(0);
327                throw new SRUException(
328                        SRUConstants.SRU_UNSUPPORTED_RELATION_MODIFIER,
329                        modifier.getValue(), "Relation modifier \"" +
330                                modifier.getValue() +
331                                "\" is not supported in scan operation.");
332            }
333
334            final String term = root.getTerm();
335            if ((term == null) || term.isEmpty()) {
336                throw new SRUException(SRUConstants.SRU_EMPTY_TERM_UNSUPPORTED,
337                        "An empty term is not supported in scan operation.");
338            }
339
340            /*
341             * generate result: currently we only have a flat hierarchy, so
342             * return an empty result on any attempt to do a recursive scan ...
343             */
344            List<ResourceInfo> results = null;
345            if ((FCS_SCAN_INDEX_CQL_SERVERCHOICE.equals(index) &&
346                    FCS_SCAN_INDEX_FCS_RESOURCE.equals(term)) ||
347                    (FCS_SCAN_INDEX_FCS_RESOURCE.equals(index))) {
348                results = resourceInfoInventory.getResourceInfoList(term);
349            }
350            if ((results == null) || results.isEmpty()) {
351                return Collections.emptyList();
352            } else {
353                return results;
354            }
355        } else {
356            throw new SRUException(SRUConstants.SRU_QUERY_FEATURE_UNSUPPORTED,
357                    "Scan clause too complex.");
358        }
359    }
360
361
362    private static void writeFullResourceInfo(XMLStreamWriter writer,
363            String prefix, List<ResourceInfo> resourceInfoList)
364            throws XMLStreamException {
365        if (resourceInfoList == null) {
366            throw new NullPointerException("resourceInfoList == null");
367        }
368        if (!resourceInfoList.isEmpty()) {
369            final boolean defaultNS = ((prefix == null) || prefix.isEmpty());
370            if (defaultNS) {
371                writer.setDefaultNamespace(FCS_RESOURCE_INFO_NS);
372            } else {
373                writer.setPrefix(prefix, FCS_RESOURCE_INFO_NS);
374            }
375            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "ResourceCollection");
376            if (defaultNS) {
377                writer.writeDefaultNamespace(FCS_RESOURCE_INFO_NS);
378            } else {
379                writer.writeNamespace(prefix, FCS_RESOURCE_INFO_NS);
380            }
381            for (ResourceInfo resourceInfo : resourceInfoList) {
382                doWriteResourceInfo(writer, prefix, resourceInfo, false, true);
383            }
384            writer.writeEndElement(); // "ResourceCollection" element
385        }
386    }
387
388
389    private static void writeResourceInfo(XMLStreamWriter writer, String prefix,
390            ResourceInfo resourceInfo) throws XMLStreamException {
391        if (resourceInfo == null) {
392            throw new NullPointerException("resourceInfo == null");
393        }
394        doWriteResourceInfo(writer, prefix, resourceInfo, true, false);
395    }
396
397
398    private static void doWriteResourceInfo(XMLStreamWriter writer,
399            String prefix, ResourceInfo resourceInfo, boolean writeNS,
400            boolean recursive) throws XMLStreamException {
401        final boolean defaultNS = ((prefix == null) || prefix.isEmpty());
402        if (writeNS) {
403            if (defaultNS) {
404                writer.setDefaultNamespace(FCS_RESOURCE_INFO_NS);
405            } else {
406                writer.setPrefix(prefix, FCS_RESOURCE_INFO_NS);
407            }
408        }
409        writer.writeStartElement(FCS_RESOURCE_INFO_NS, "ResourceInfo");
410        if (writeNS) {
411            if (defaultNS) {
412                writer.writeDefaultNamespace(FCS_RESOURCE_INFO_NS);
413            } else {
414                writer.writeNamespace(prefix, FCS_RESOURCE_INFO_NS);
415            }
416        }
417        if (recursive) {
418            /*
419             * HACK: only output @pid for recursive (= explain) requests.
420             * This should be revisited, if we decide to go for the explain
421             * style enumeration of resources.
422             */
423            writer.writeAttribute("pid", resourceInfo.getPid());
424        }
425        if (resourceInfo.hasSubResources()) {
426            writer.writeAttribute("hasSubResources", "true");
427        }
428
429        final Map<String, String> title = resourceInfo.getTitle();
430        for (Map.Entry<String, String> i : title.entrySet()) {
431            writer.setPrefix(XMLConstants.XML_NS_PREFIX,
432                    XMLConstants.XML_NS_URI);
433            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Title");
434            writer.writeAttribute(XMLConstants.XML_NS_URI, "lang", i.getKey());
435            writer.writeCharacters(i.getValue());
436            writer.writeEndElement(); // "title" element
437        }
438
439        final Map<String, String> description = resourceInfo.getDescription();
440        if (description != null) {
441            for (Map.Entry<String, String> i : description.entrySet()) {
442                writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Description");
443                writer.writeAttribute(XMLConstants.XML_NS_URI, "lang",
444                        i.getKey());
445                writer.writeCharacters(i.getValue());
446                writer.writeEndElement(); // "Description" element
447            }
448        }
449
450        final String landingPageURI = resourceInfo.getLandingPageURI();
451        if (landingPageURI != null) {
452            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "LandingPageURI");
453            writer.writeCharacters(landingPageURI);
454            writer.writeEndElement(); // "LandingPageURI" element
455        }
456
457        final List<String> languages = resourceInfo.getLanguages();
458        writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Languages");
459        for (String i : languages) {
460            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Language");
461            writer.writeCharacters(i);
462            writer.writeEndElement(); // "Language" element
463
464        }
465        writer.writeEndElement(); // "Languages" element
466
467        if (recursive && resourceInfo.hasSubResources()) {
468            writer.writeStartElement(FCS_RESOURCE_INFO_NS,
469                    "ResourceInfoCollection");
470            for (ResourceInfo r : resourceInfo.getSubResources()) {
471                doWriteResourceInfo(writer, prefix, r, writeNS, recursive);
472            }
473            writer.writeEndElement(); // "ResourceCollection" element
474        }
475        writer.writeEndElement(); // "ResourceInfo" element
476    }
477
478} // class SimpleEndpointSearchEngineBase
Note: See TracBrowser for help on using the repository browser.