Changeset 5546


Ignore:
Timestamp:
08/09/14 20:45:17 (10 years ago)
Author:
Oliver Schonefeld
Message:
  • support new FCS specification (with some backwards compatibility for old spec)

HEADS UP: not yet ready for release; needs more testing

Location:
FCSSimpleEndpoint/trunk
Files:
2 added
4 deleted
5 edited
2 moved

Legend:

Unmodified
Added
Removed
  • FCSSimpleEndpoint/trunk/pom.xml

    r5477 r5546  
    44    <groupId>eu.clarin.sru.fcs</groupId>
    55    <artifactId>fcs-simple-endpoint</artifactId>
    6     <version>2.0-SNAPSHOT</version>
     6    <version>1.3.0-SNAPSHOT</version>
    77    <packaging>jar</packaging>
    88    <name>A simple CLARIN FCS endpoint</name>
     
    1818        <maven.compiler.target>1.6</maven.compiler.target>
    1919        <!-- versions of common dependencies -->
    20         <slf4j.version>1.7.2</slf4j.version>
     20        <slf4j.version>1.7.7</slf4j.version>
    2121    </properties>
    2222
     
    4545            <artifactId>woodstox-core-lgpl</artifactId>
    4646            <version>4.1.3</version>
     47                        <exclusions>
     48                                <!-- StAX comes with Java 1.6 -->
     49                                <exclusion>
     50                                        <artifactId>stax-api</artifactId>
     51                                        <groupId>javax.xml.stream</groupId>
     52                                </exclusion>
     53                                <exclusion>
     54                                        <artifactId>stax-api</artifactId>
     55                                        <groupId>stax</groupId>
     56                                </exclusion>
     57                        </exclusions>
    4758        </dependency>
    4859    </dependencies>
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/DataView.java

    r5477 r5546  
    11package eu.clarin.sru.server.fcs;
    22
     3/**
     4 * This class is used to hold information about a data view that is implemented
     5 * by the endpoint.
     6 */
    37public class DataView {
     8    /**
     9     * Enumeration to indicate the delivery policy of a data view.
     10     */
     11    public enum DeliveryPolicy {
     12        /**
     13         * The data view is sent automatically  by the endpoint.
     14         */
     15        SEND_BY_DEFAULT,
     16        /**
     17         * A client must explicitly request the endpoint.
     18         */
     19        NEED_TO_REQUEST;
     20    } // enum PayloadDelivery
     21    private final String identifier;
     22    private final String mimeType;
     23    private final DeliveryPolicy deliveryPolicy;
    424
    5         public enum PayloadDelivery {
    6                 SEND_BY_DEFAULT, NEED_TO_REQUEST;
    7                
    8                 @Override
    9                 public String toString(){
    10                         String str = super.toString().toLowerCase();
    11                         return str.replace("_", "-");
    12                 }
    13         }
    1425
    15         public enum PayloadDisposition {
    16                 INLINE, REFERENCE;
    17                
    18                 @Override
    19                 public String toString(){
    20                         return super.toString().toLowerCase();
    21                 }
    22         }
    23        
    24         private String description;
    25         private String mimeType;
    26         private String payloadDisposition;
    27         private String payloadDelivery;
    28         private String shortIdentifier;
    29        
    30         public DataView() {     }
    31         public DataView(String description, String mimeType,
    32                         PayloadDisposition payloadDisposition, PayloadDelivery payloadDelivery,
    33                         String shortId){
    34                 this.description = description;
    35                 this.mimeType = mimeType;
    36                 this.payloadDisposition = payloadDisposition.toString();
    37                 this.payloadDelivery = payloadDelivery.toString();
    38                 this.shortIdentifier = shortId;         
    39         }
    40        
    41        
    42         public String getDescription() {
    43                 return description;
    44         }
    45         public void setDescription(String description) {
    46                 this.description = description;
    47         }
    48         public String getMimeType() {
    49                 return mimeType;
    50         }
    51         public void setMimeType(String mimeType) {
    52                 this.mimeType = mimeType;
    53         }
    54         public String getPayloadDisposition() {
    55                 return payloadDisposition;
    56         }
    57         public void setPayloadDisposition(String payloadDisposition) {
    58                 this.payloadDisposition = payloadDisposition;
    59         }
    60         public String getPayloadDelivery() {
    61                 return payloadDelivery;
    62         }
    63         public void setPayloadDelivery(String payloadDelivery) {
    64                 this.payloadDelivery = payloadDelivery;
    65         }
    66         public String getShortIdentifier() {
    67                 return shortIdentifier;
    68         }
    69         public void setShortIdentifier(String shortIdentifier) {
    70                 this.shortIdentifier = shortIdentifier;
    71         }
    72 }
     26    /**
     27     * Constructor.
     28     *
     29     * @param identifier
     30     *            a unique short identifier for the data view
     31     * @param mimeType
     32     *            the MIME type of the data view
     33     * @param deliveryPolicy
     34     *            the delivery policy for this data view
     35     */
     36    public DataView(String identifier, String mimeType,
     37            DeliveryPolicy deliveryPolicy) {
     38        if (identifier == null) {
     39            throw new NullPointerException("identifier == null");
     40        }
     41        if (identifier.isEmpty()) {
     42            throw new IllegalArgumentException("identifier is empty");
     43        }
     44        this.identifier = identifier;
     45
     46        if (mimeType == null) {
     47            throw new NullPointerException("mimeType == null");
     48        }
     49        if (mimeType.isEmpty()) {
     50            throw new IllegalArgumentException("mimeType is empty");
     51        }
     52        this.mimeType = mimeType;
     53
     54        if (deliveryPolicy == null) {
     55            throw new NullPointerException("deliveryPolicy == null");
     56        }
     57        this.deliveryPolicy = deliveryPolicy;
     58    }
     59
     60
     61    public String getIdentifier() {
     62        return identifier;
     63    }
     64
     65
     66    public String getMimeType() {
     67        return mimeType;
     68    }
     69
     70
     71    public DeliveryPolicy getDeliveryPolicy() {
     72        return deliveryPolicy;
     73    }
     74
     75
     76    @Override
     77    public String toString() {
     78        StringBuilder sb = new StringBuilder();
     79        sb.append(getClass().getSimpleName());
     80        sb.append("[");
     81        sb.append("identifier=").append(identifier);
     82        sb.append(", mimeType=").append(mimeType);
     83        sb.append("]");
     84        return sb.toString();
     85    }
     86} // class DataView
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/ResourceInfo.java

    r5485 r5546  
    99 * This class implements a resource info record, which provides supplementary
    1010 * information about a resource that is available at the endpoint.
    11  * 
    12  * @see ResourceInfoInventory
     11 *
     12 * @see EndpointDescription
    1313 */
    1414public class ResourceInfo {
    1515    private final String pid;
    16     @Deprecated
    17     private final int resourceCount;
    1816    private final Map<String, String> title;
    1917    private final Map<String, String> description;
    2018    private final String landingPageURI;
    2119    private final List<String> languages;
    22     private List<String> availableDataViews;
    23     private List<ResourceInfo> subResources;
    24    
     20    private final List<DataView> availableDataViews;
     21    private final List<ResourceInfo> subResources;
     22
    2523
    2624    /**
     
    2927     * @param pid
    3028     *            the persistent identifier of the resource
    31      * @param resourceCount
    32      *            the number of items within the resource or <code>-1</code> if
    33      *            not applicable
    3429     * @param title
    3530     *            the title of the resource represented as a map with pairs of
     
    4540     *            the languages represented within this resource represented as
    4641     *            a list of ISO-632-3 three letter language codes
     42     * @param availableDataViews
     43     *            the list of available data views for this resource
    4744     * @param subResources
    4845     *            a list of resource sub-ordinate to this resource or
    4946     *            <code>null</code> if not applicable
    5047     */
    51     public ResourceInfo(String pid, int resourceCount,
    52             Map<String, String> title, Map<String, String> description,
    53             String landingPageURI, List<String> languages,
     48    public ResourceInfo(String pid, Map<String, String> title,
     49            Map<String, String> description, String landingPageURI,
     50            List<String> languages, List<DataView> availableDataViews,
    5451            List<ResourceInfo> subResources) {
    5552        if (pid == null) {
    56             throw new NullPointerException("id == null");
     53            throw new NullPointerException("pid == null");
    5754        }
    5855        this.pid = pid;
    59         this.resourceCount = (resourceCount > 0) ? resourceCount : -1;
     56
    6057        if (title == null) {
    6158            throw new NullPointerException("title == null");
     
    7067            this.description = null;
    7168        }
     69
    7270        this.landingPageURI = landingPageURI;
    7371        if (languages == null) {
     
    7876        }
    7977        this.languages = languages;
    80        
     78
     79        if (availableDataViews == null) {
     80            throw new IllegalArgumentException("availableDataViews == null");
     81        }
     82        this.availableDataViews =
     83                Collections.unmodifiableList(availableDataViews);
     84
    8185        if ((subResources != null) && !subResources.isEmpty()) {
    8286            this.subResources = Collections.unmodifiableList(subResources);
    8387        } else {
    8488            this.subResources = null;
    85         }       
     89        }
    8690    }
    8791
    88     public ResourceInfo(String pid, int resourceCount,
    89             Map<String, String> title, Map<String, String> description,
    90             String landingPageURI, List<String> languages,
    91             List<String> availableDataViews,
    92             List<ResourceInfo> subResources) {
    93         this(pid,resourceCount, title, description, landingPageURI, languages, subResources);
    94        
    95         if (availableDataViews == null){
    96                 throw new IllegalArgumentException("available data views == null");
    97         }
    98         if (availableDataViews.isEmpty()){
    99                 throw new IllegalArgumentException("available data views are empty");
    100         }
    101         this.availableDataViews = availableDataViews;       
    102     }
    103    
    10492
    10593    /**
     
    11098    public String getPid() {
    11199        return pid;
    112     }
    113 
    114 
    115     /**
    116      * Get the number of resources within this resource. If this resource has
    117      * sub-ordinate resources, this number should be the sum of all items within
    118      * the sub-ordinate resources plus the number of items within this resource.
    119      *
    120      * @return a number of items or <code>-1</code> if not applicable
    121      */
    122     @Deprecated
    123     public int getResourceCount() {
    124         return resourceCount;
    125100    }
    126101
     
    217192
    218193
    219         public List<String> getAvailableDataViews() {
     194        public List<DataView> getAvailableDataViews() {
    220195                return availableDataViews;
    221196        }
    222197
    223 
    224198} // class ResourceInfo
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/SimpleEndpointSearchEngineBase.java

    r5485 r5546  
    11package eu.clarin.sru.server.fcs;
    22
    3 import java.util.ArrayList;
     3import java.net.URI;
    44import java.util.Collections;
    55import java.util.List;
     
    77
    88import javax.servlet.ServletContext;
     9import javax.xml.XMLConstants;
    910import javax.xml.stream.XMLStreamException;
    1011import javax.xml.stream.XMLStreamWriter;
     
    3031
    3132/**
    32  * A base class for implementing a simple search engine to be used as a CLARIN
    33  * FCS endpoint.
     33 * A base class for implementing a simple search engine to be used as a
     34 * CLARIN-FCS endpoint.
    3435 *
    3536 */
    3637public abstract class SimpleEndpointSearchEngineBase extends
    3738        SRUSearchEngineBase {
    38    
     39    private static final String X_FCS_ENDPOINT_DESCRIPTION = "x-fcs-endpoint-description";
    3940    private static final String X_CMD_RESOURCE_INFO = "x-cmd-resource-info";
    40     private static final String X_FCS_ENDPOINT_DESCRIPTION = "x-fcs-endpoint-description";
    41    
     41    private static final String ED_NS = "http://clarin.eu/fcs/endpoint-description";
     42    private static final String ED_PREFIX = "ed";
     43    private static final int ED_VERSION = 1;
     44    private static final String FCS_RESOURCE_INFO_NS = "http://clarin.eu/fcs/1.0/resource-info";
    4245    private static final String FCS_SCAN_INDEX_FCS_RESOURCE = "fcs.resource";
    4346    private static final String FCS_SCAN_INDEX_CQL_SERVERCHOICE = "cql.serverChoice";
     
    4750    private static final Logger logger =
    4851            LoggerFactory.getLogger(SimpleEndpointSearchEngineBase.class);
    49     protected ResourceInfoInventory resourceInfoInventory;
    50    
    51     public List<String> capabilities;
    52     public List<DataView> supportedDataViews;
    53    
    54     public void addEndpointCapability(String c){
    55         capabilities.add(c);
    56     }
    57    
    58     public void addDataView(DataView d){
    59         supportedDataViews.add(d);
    60     }   
    61        
    62     public void setCapabilities(){
    63          logger.debug("Setting basic capability");
    64          capabilities = new ArrayList<String>();
    65          capabilities.add("http://clarin.eu/fcs/capability/basic-search");
    66     }     
    67    
    68     public void setSupportedDataViews(){
    69          logger.debug("Setting Generic Hits dataview");
    70          supportedDataViews = new ArrayList<DataView>();
    71          supportedDataViews.add(
    72                         new DataView("The representation of the hit",
    73                                 "application/x-clarin-fcs-hits+xml",
    74                                 DataView.PayloadDisposition.INLINE,
    75                                 DataView.PayloadDelivery.SEND_BY_DEFAULT,
    76                                 "hits")
    77          );
    78     }
    79    
    80         /**
     52    protected EndpointDescription endpointDescription;
     53
     54
     55    /**
    8156     * This method should not be overridden. Perform your custom initialization
    8257     * in the {@link #doInit(ServletContext, SRUServerConfig, Map)} method
     
    8964            Map<String, String> params) throws SRUConfigException {
    9065        logger.debug("initializing");
    91         super.init(context, config, params);             
    92        
     66        super.init(context, config, params);
     67
    9368        logger.debug("initializing search engine implementation");
    9469        doInit(context, config, params);
    95                
    96         logger.debug("initizalizing resource info inventory");
    97         this.resourceInfoInventory = createResourceInfoInventory(context, config, params);
    98         if (this.resourceInfoInventory == null) {
    99             logger.error("ClarinFCSSearchEngineBase implementation error: " +
    100                     "initResourceCatalog() returned null");
    101             throw new SRUConfigException("initResourceCatalog() returned no " +
    102                     "valid implementation of a ResourceCatalog");
     70
     71        logger.debug("initizalizing endpoint description");
     72        this.endpointDescription =
     73                createEndpointDescription(context, config, params);
     74        if (this.endpointDescription == null) {
     75            logger.error("SimpleEndpointSearchEngineBase implementation " +
     76                    "error: createEndpointDescription() returned null");
     77            throw new SRUConfigException("createEndpointDescription() " +
     78                    "returned no valid implementation of an EndpointDescription");
    10379        }
    10480    }
     
    11389    @Override
    11490    public final void destroy() {
    115         logger.debug("performing cleanup of resource info inventory");
    116         resourceInfoInventory.destroy();
     91        logger.debug("performing cleanup of endpoint description");
     92        endpointDescription.destroy();
    11793        logger.debug("performing cleanup of search engine");
    11894        doDestroy();
     
    125101            SRURequest request, SRUDiagnosticList diagnostics)
    126102            throws SRUException {
    127        
    128         final boolean provideCmdResourceInfo =
    129                 parseBoolean(request.getExtraRequestData(X_CMD_RESOURCE_INFO));
    130         final boolean provideFcsResourceInfo =
    131                 parseBoolean(request.getExtraRequestData(X_FCS_ENDPOINT_DESCRIPTION));
    132                
    133         if (provideCmdResourceInfo) {
    134             final List<ResourceInfo> resourceInfoList =
    135                     resourceInfoInventory.getResourceInfoList(
    136                             ResourceInfoInventory.PID_ROOT);
    137            
     103
     104        final boolean provideEndpointDescription =
     105                parseBoolean(request.getExtraRequestData(
     106                        X_FCS_ENDPOINT_DESCRIPTION));
     107
     108        if (provideEndpointDescription) {
    138109            return new SRUExplainResult(diagnostics) {
    139110                @Override
    140111                public boolean hasExtraResponseData() {
    141                     return provideCmdResourceInfo;
    142                 }
     112                    return provideEndpointDescription;
     113                }
     114
     115
    143116                @Override
    144117                public void writeExtraResponseData(XMLStreamWriter writer)
    145118                        throws XMLStreamException {
    146                     ResourceInfoWriter.writeFullResourceInfo(writer, null, resourceInfoList);
     119                    writeEndpointDescription(writer);
    147120                }
    148121            };
    149         }
    150         else if (provideFcsResourceInfo && capabilities != null
    151                         && supportedDataViews != null){
    152                
    153                 final List<ResourceInfo> resourceInfoList =
    154                     resourceInfoInventory.getResourceInfoList(
    155                             ResourceInfoInventory.PID_ROOT);
    156                
    157 //              if (capabilities == null){
    158 //                      throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
    159 //                                      "Capabilities are not set.");
    160 //              }
    161 //              if (supportedDataViews == null){
    162 //                      throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
    163 //                                      "Supported data views are not set.");
    164 //              }
    165                 return new SRUExplainResult(diagnostics) {
    166                 @Override
    167                 public boolean hasExtraResponseData() {
    168                     return provideFcsResourceInfo;
    169                 }
    170                 @Override
    171                 public void writeExtraResponseData(XMLStreamWriter writer)
    172                         throws XMLStreamException {
    173                         EndpointDescriptionWriter.writeEndpointDescription(writer,
    174                                 capabilities, supportedDataViews, resourceInfoList);
    175                 }
    176             };
    177         }
    178         else {
     122        } else {
    179123            return null;
    180124        }
     
    191135     */
    192136    @Override
    193     @Deprecated
    194137    public final SRUScanResultSet scan(SRUServerConfig config,
    195138            SRURequest request, SRUDiagnosticList diagnostics)
     
    215158             */
    216159            final boolean provideResourceInfo = parseBoolean(
    217                     request.getExtraRequestData(X_FCS_ENDPOINT_DESCRIPTION));
     160                    request.getExtraRequestData(X_CMD_RESOURCE_INFO));
    218161
    219162            return new SRUScanResultSet(diagnostics) {
     
    234177                @Override
    235178                public int getNumberOfRecords() {
    236                     return result.get(idx).getResourceCount();
     179                    return -1;
    237180                }
    238181
     
    260203                        throws XMLStreamException {
    261204                    if (provideResourceInfo) {
    262                         ResourceInfoWriter.writeResourceInfo(writer, null, result.get(idx));
     205                        writeLegacyResourceInfo(writer, result.get(idx));
    263206                    }
    264207                }
     
    270213
    271214
    272 
    273     /**
    274      * Create the resource info inventory to be used with this endpoint.
    275      * Implement this method to provide an implementation of a
    276      * {@link ResourceInfoInventory} that is tailored towards your environment
    277      * and needs.
    278      *
    279      * @param context
    280      *            the {@link ServletContext} for the Servlet
    281      * @param config
    282      *            the {@link SRUServerConfig} object for this search engine
    283      * @param params
    284      *            additional parameters gathered from the Servlet configuration
    285      *            and Servlet context.
    286      * @return an instance of a {@link ResourceInfoInventory} used by this
    287      *         search engine
    288      * @throws SRUConfigException
    289      *             if an error occurred
    290      */
    291     protected abstract ResourceInfoInventory createResourceInfoInventory(
     215    protected abstract EndpointDescription createEndpointDescription(
    292216            ServletContext context, SRUServerConfig config,
    293217            Map<String, String> params) throws SRUConfigException;
     
    328252     *      SRUDiagnosticList)
    329253     */
    330     @Deprecated
    331254    protected SRUScanResultSet doScan(SRUServerConfig config,
    332255            SRURequest request, SRUDiagnosticList diagnostics)
     
    364287    }
    365288
    366     @Deprecated
     289
    367290    private List<ResourceInfo> translateFcsScanResource(CQLNode scanClause)
    368291            throws SRUException {
     
    386309            }
    387310
     311            logger.warn("scan on 'fcs.resource' for endpoint resource " +
     312                    "enumeration is deprecated.");
    388313
    389314            // only allow "=" relation without any modifiers
     
    422347                    FCS_SCAN_INDEX_FCS_RESOURCE.equals(term)) ||
    423348                    (FCS_SCAN_INDEX_FCS_RESOURCE.equals(index))) {
    424                 results = resourceInfoInventory.getResourceInfoList(term);
     349                results = endpointDescription.getResourceList(term);
    425350            }
    426351            if ((results == null) || results.isEmpty()) {
     
    436361
    437362
    438    
     363    private void writeEndpointDescription(XMLStreamWriter writer)
     364            throws XMLStreamException {
     365        writer.setPrefix(ED_PREFIX, ED_NS);
     366        writer.writeStartElement(ED_NS, "EndpointDescription");
     367        writer.writeNamespace(ED_PREFIX, ED_NS);
     368        writer.writeAttribute("version", Integer.toString(ED_VERSION));
     369
     370        // capabilities
     371        writer.writeStartElement(ED_NS, "Capabilities");
     372        for (URI capability : endpointDescription.getCapabilities()) {
     373            writer.writeStartElement(ED_NS, "Capability");
     374            writer.writeCharacters(capability.toString());
     375            writer.writeEndElement(); // "Capability" element
     376        }
     377        writer.writeEndElement(); // "Capabilities" element
     378
     379        // supported data views
     380        writer.writeStartElement(ED_NS, "SupportedDataViews");
     381        for (DataView dataView : endpointDescription.getSupportedDataViews()) {
     382            writer.writeStartElement(ED_NS, "SupportedDataView");
     383            writer.writeAttribute("id", dataView.getIdentifier());
     384            String s;
     385            switch (dataView.getDeliveryPolicy()) {
     386            case SEND_BY_DEFAULT:
     387                s = "send-by-default";
     388                break;
     389            case NEED_TO_REQUEST:
     390                s = "need-to-request";
     391                break;
     392            default:
     393                throw new XMLStreamException(
     394                        "invalid value for payload delivery policy: " +
     395                                dataView.getDeliveryPolicy());
     396            } // switch
     397            writer.writeAttribute("delivery-policy", s);
     398            writer.writeCharacters(dataView.getMimeType());
     399            writer.writeEndElement(); // "SupportedDataView" element
     400        }
     401        writer.writeEndElement(); // "SupportedDataViews" element
     402
     403        try {
     404            // resources
     405            List<ResourceInfo> resources =
     406                    endpointDescription.getResourceList(
     407                            EndpointDescription.PID_ROOT);
     408            writeResourceInfos(writer, resources);
     409        } catch (SRUException e) {
     410            throw new XMLStreamException("error retriving top-level resources",
     411                    e);
     412        }
     413        writer.writeEndElement(); // "EndpointDescription" element
     414    }
     415
     416
     417    private void writeResourceInfos(XMLStreamWriter writer,
     418            List<ResourceInfo> resources) throws XMLStreamException {
     419        if (resources == null) {
     420            throw new NullPointerException("resources == null");
     421        }
     422        if (!resources.isEmpty()) {
     423            writer.writeStartElement(ED_NS, "Resources");
     424
     425            for (ResourceInfo resource : resources) {
     426                writer.writeStartElement(ED_NS, "Resource");
     427                writer.writeAttribute("pid", resource.getPid());
     428
     429                // title
     430                final Map<String, String> title = resource.getTitle();
     431                for (Map.Entry<String, String> i : title.entrySet()) {
     432                    writer.setPrefix(XMLConstants.XML_NS_PREFIX,
     433                            XMLConstants.XML_NS_URI);
     434                    writer.writeStartElement(ED_NS, "Title");
     435                    writer.writeAttribute(XMLConstants.XML_NS_URI, "lang", i.getKey());
     436                    writer.writeCharacters(i.getValue());
     437                    writer.writeEndElement(); // "title" element
     438                }
     439
     440                // description
     441                final Map<String, String> description = resource.getDescription();
     442                if (description != null) {
     443                    for (Map.Entry<String, String> i : description.entrySet()) {
     444                        writer.writeStartElement(ED_NS, "Description");
     445                        writer.writeAttribute(XMLConstants.XML_NS_URI, "lang",
     446                                i.getKey());
     447                        writer.writeCharacters(i.getValue());
     448                        writer.writeEndElement(); // "Description" element
     449                    }
     450                }
     451
     452                // landing page
     453                final String landingPageURI = resource.getLandingPageURI();
     454                if (landingPageURI != null) {
     455                    writer.writeStartElement(ED_NS, "LandingPageURI");
     456                    writer.writeCharacters(landingPageURI);
     457                    writer.writeEndElement(); // "LandingPageURI" element
     458                }
     459
     460                // languages
     461                final List<String> languages = resource.getLanguages();
     462                writer.writeStartElement(ED_NS, "Languages");
     463                for (String i : languages) {
     464                    writer.writeStartElement(ED_NS, "Language");
     465                    writer.writeCharacters(i);
     466                    writer.writeEndElement(); // "Language" element
     467
     468                }
     469                writer.writeEndElement(); // "Languages" element
     470
     471                // available data views
     472                StringBuilder sb = new StringBuilder();
     473                for (DataView dataview : resource.getAvailableDataViews()) {
     474                    if (sb.length() > 0) {
     475                        sb.append(" ");
     476                    }
     477                    sb.append(dataview.getIdentifier());
     478                }
     479                writer.writeEmptyElement(ED_NS, "AvailableDataViews");
     480                writer.writeAttribute("ref", sb.toString());
     481
     482                // child resources
     483                List<ResourceInfo> subs = resource.getSubResources();
     484                if ((subs != null) && !subs.isEmpty()) {
     485                    writeResourceInfos(writer, subs);
     486                }
     487
     488                writer.writeEndElement(); // "Resource" element
     489            }
     490            writer.writeEndElement(); // "Resources" element
     491        }
     492    }
     493
     494
     495    private void writeLegacyResourceInfo(XMLStreamWriter writer,
     496            ResourceInfo resourceInfo) throws XMLStreamException {
     497        writer.setDefaultNamespace(FCS_RESOURCE_INFO_NS);
     498        writer.writeStartElement(FCS_RESOURCE_INFO_NS, "ResourceInfo");
     499        writer.writeDefaultNamespace(FCS_RESOURCE_INFO_NS);
     500
     501        // title
     502        final Map<String, String> title = resourceInfo.getTitle();
     503        for (Map.Entry<String, String> i : title.entrySet()) {
     504            writer.setPrefix(XMLConstants.XML_NS_PREFIX,
     505                    XMLConstants.XML_NS_URI);
     506            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Title");
     507            writer.writeAttribute(XMLConstants.XML_NS_URI, "lang", i.getKey());
     508            writer.writeCharacters(i.getValue());
     509            writer.writeEndElement(); // "title" element
     510        }
     511
     512        // description
     513        final Map<String, String> description = resourceInfo.getDescription();
     514        if (description != null) {
     515            for (Map.Entry<String, String> i : description.entrySet()) {
     516                writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Description");
     517                writer.writeAttribute(XMLConstants.XML_NS_URI, "lang",
     518                        i.getKey());
     519                writer.writeCharacters(i.getValue());
     520                writer.writeEndElement(); // "Description" element
     521            }
     522        }
     523
     524        // landing page
     525        final String landingPageURI = resourceInfo.getLandingPageURI();
     526        if (landingPageURI != null) {
     527            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "LandingPageURI");
     528            writer.writeCharacters(landingPageURI);
     529            writer.writeEndElement(); // "LandingPageURI" element
     530        }
     531
     532        // languages
     533        final List<String> languages = resourceInfo.getLanguages();
     534        writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Languages");
     535        for (String i : languages) {
     536            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Language");
     537            writer.writeCharacters(i);
     538            writer.writeEndElement(); // "Language" element
     539
     540        }
     541        writer.writeEndElement(); // "Languages" element
     542        writer.writeEndElement(); // "ResourceInfo" element
     543    }
     544
     545
     546//             final boolean defaultNS = ((prefix == null) || prefix.isEmpty());
     547//            if (writeNS) {
     548//                 if (defaultNS) {
     549//                     writer.setDefaultNamespace(FCS_RESOURCE_INFO_NS);
     550//                } else {
     551//                    writer.setPrefix(prefix, FCS_RESOURCE_INFO_NS);
     552//                }
     553//            }
     554//            writer.writeStartElement(FCS_RESOURCE_INFO_NS, "ResourceInfo");
     555//            if (writeNS) {
     556//                if (defaultNS) {
     557//                    writer.writeDefaultNamespace(FCS_RESOURCE_INFO_NS);
     558//                 } else {
     559//                    writer.writeNamespace(prefix, FCS_RESOURCE_INFO_NS);
     560//                 }
     561//          } )
     562//}
     563
     564//    public static void XwriteResourceInfo(XMLStreamWriter writer, String prefix,
     565//            ResourceInfo resourceInfo) throws XMLStreamException {
     566//        doWriteResourceInfo(writer, prefix, resourceInfo, true, false);
     567//    }
     568//
     569//
     570//    private static void XdoWriteResourceInfo(XMLStreamWriter writer,
     571//            String prefix, ResourceInfo resourceInfo, boolean writeNS,
     572//            boolean recursive) throws XMLStreamException {
     573//
     574//        if (writer == null) {
     575//            throw new NullPointerException("writer == null");
     576//        }
     577//        if (resourceInfo == null) {
     578//            throw new NullPointerException("resourceInfo == null");
     579//        }
     580//
     581//        final boolean defaultNS = ((prefix == null) || prefix.isEmpty());
     582//        if (writeNS) {
     583//            if (defaultNS) {
     584//                writer.setDefaultNamespace(FCS_RESOURCE_INFO_NS);
     585//            } else {
     586//                writer.setPrefix(prefix, FCS_RESOURCE_INFO_NS);
     587//            }
     588//        }
     589//        writer.writeStartElement(FCS_RESOURCE_INFO_NS, "Resource");
     590//        if (writeNS) {
     591//            if (defaultNS) {
     592//                writer.writeDefaultNamespace(FCS_RESOURCE_INFO_NS);
     593//            } else {
     594//                writer.writeNamespace(prefix, FCS_RESOURCE_INFO_NS);
     595//            }
     596//        }
     597//        if (recursive) {
     598//            /*
     599//             * HACK: only output @pid for recursive (= explain) requests.
     600//             * This should be revisited, if we decide to go for the explain
     601//             * style enumeration of resources.
     602//             */
     603//            writer.writeAttribute("pid", resourceInfo.getPid());
     604//        }
     605//        if (resourceInfo.hasSubResources()) {
     606//            writer.writeAttribute("hasSubResources", "true");
     607//        }
     608//
     609//
     610//        if (recursive && resourceInfo.hasSubResources()) {
     611//            writer.writeStartElement(FCS_RESOURCE_INFO_NS,
     612//                    "ResourceInfoCollection");
     613//            for (ResourceInfo r : resourceInfo.getSubResources()) {
     614//                doWriteResourceInfo(writer, prefix, r, writeNS, recursive);
     615//            }
     616//            writer.writeEndElement(); // "ResourceCollection" element
     617//        }
     618//        writer.writeEndElement(); // "ResourceInfo" element
     619//    }
    439620
    440621} // class SimpleEndpointSearchEngineBase
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/XMLStreamWriterHelper.java

    r2751 r5546  
    88 * This class provides several helper methods for writing records in the
    99 * CLARIN-FCS record schema. These methods <em>do not</em> cover the full
    10  * spectrum of all variations of records that are permitted by the CLARIN FCS
     10 * spectrum of all variations of records that are permitted by the CLARIN-FCS
    1111 * specification.
    1212 *
    1313 * @see <a
    14  *      href="https://trac.clarin.eu/wiki/FCS-specification#SearchRetrieveOperation">
    15  *      CLARIN FCS specification, section "SearchRetrieve Operation"</a>
     14 *      href="https://trac.clarin.eu/wiki/FCS/Specification">
     15 *      CLARIN FCS specification, section "Operation searchRetrieve"</a>
    1616 */
    1717public final class XMLStreamWriterHelper {
     
    2323    private static final String FCS_KWIC_MIMETYPE =
    2424            "application/x-clarin-fcs-kwic+xml";
     25    private static final String FCS_HITS_NS =
     26            "http://clarin.eu/fcs/dataview/hits";
     27    private static final String FCS_HITS_PREFIX = "hits";
     28    private static final String FCS_HITS_MIMETYPE =
     29            "application/x-clarin-fcs-hits+xml";
    2530
    2631
     
    205210     * @throws XMLStreamException
    206211     *             if an error occurred
    207      */
     212     * @deprecated The the HITS data view instead.
     213     */
     214    @Deprecated
    208215    public static void writeKWICDataView(XMLStreamWriter writer, String left,
    209216            String keyword, String right) throws XMLStreamException {
    210217        if (writer == null) {
    211218            throw new NullPointerException("writer == null");
     219        }
     220        if (keyword == null) {
     221            throw new NullPointerException("keyword == null");
    212222        }
    213223
     
    246256     * Convince method for writing a record with a KWIC data view. The following
    247257     * code (arguments omitted) would accomplish the same result:
     258     *
    248259     * <pre>
    249260     * ...
     
    272283     * @throws XMLStreamException
    273284     *             if an error occurred
    274      */
     285     * @deprecated The the HITS data view instead.
     286     */
     287    @Deprecated
    275288    public static void writeResourceWithKWICDataView(XMLStreamWriter writer,
    276289            String pid, String ref, String left, String keyword, String right)
     
    285298    }
    286299
     300
     301
     302    /**
     303     * Convince method to write a simple HITS data view. It automatically
     304     * performs the calls to
     305     * {@link #writeStartDataView(XMLStreamWriter, String)} and
     306     * {@link #writeEndDataView(XMLStreamWriter)}.
     307     *
     308     * @param writer
     309     *            the {@link XMLStreamWriter} to be used
     310     * @param left
     311     *            the left context of the hit or <code>null</code> if not
     312     *            applicable
     313     * @param hit
     314     *            the actual hit, that will be highlighted
     315     * @param right
     316     *            the right context of the hit or <code>null</code> if not
     317     *            applicable
     318     * @throws XMLStreamException
     319     *             if an error occurred
     320     */
     321    public static void writeHitsDataView(XMLStreamWriter writer, String left,
     322            String hit, String right) throws XMLStreamException {
     323        if (writer == null) {
     324            throw new NullPointerException("writer == null");
     325        }
     326        if (hit == null) {
     327            throw new NullPointerException("hit == null");
     328        }
     329
     330        writeStartDataView(writer, FCS_HITS_MIMETYPE);
     331
     332        // actual "hits" data view
     333        writer.setPrefix(FCS_HITS_PREFIX, FCS_HITS_NS);
     334        writer.writeStartElement(FCS_HITS_NS, "Result");
     335        writer.writeNamespace(FCS_HITS_PREFIX, FCS_HITS_NS);
     336
     337        if ((left != null) && !left.isEmpty()) {
     338            writer.writeCharacters(left);
     339        }
     340
     341        writer.writeStartElement(FCS_HITS_NS, "Hit");
     342        writer.writeCharacters(hit);
     343        writer.writeEndElement(); // "Hit" element
     344
     345        if ((right != null) && !right.isEmpty()) {
     346            writer.writeCharacters(right);
     347        }
     348
     349        writer.writeEndElement(); // "Result" element
     350
     351        writeEndDataView(writer);
     352    }
     353
     354
     355    /**
     356     * Convince method for writing a record with a KWIC data view. The following
     357     * code (arguments omitted) would accomplish the same result:
     358     *
     359     * <pre>
     360     * ...
     361     * writeStartResource(...);
     362     * writeHitsDataView(...);
     363     * writeEndResource(...);
     364     * ...
     365     * </pre>
     366     *
     367     * @param writer
     368     *            the {@link XMLStreamWriter} to be used
     369     * @param pid
     370     *            the persistent identifier of this resource or
     371     *            <code>null</code>, if not applicable
     372     * @param ref
     373     *            the reference of this resource or <code>null</code>, if not
     374     *            applicable
     375     * @param left
     376     *            the left context of the hit or <code>null</code> if not
     377     *            applicable
     378     * @param hit
     379     *            the actual hit, that will be highlighted
     380     * @param right
     381     *            the right context of the hit or <code>null</code> if not
     382     *            applicable
     383     * @throws XMLStreamException
     384     *             if an error occurred
     385     */
     386    public static void writeResourceWithHitsDataView(XMLStreamWriter writer,
     387            String pid, String ref, String left, String hit, String right)
     388            throws XMLStreamException {
     389        if (writer == null) {
     390            throw new NullPointerException("writer == null");
     391        }
     392
     393        writeStartResource(writer, pid, ref);
     394        writeHitsDataView(writer, left, hit, right);
     395        writeEndResource(writer);
     396    }
     397
     398
     399    /**
     400     * Convince method to write a simple HITS data view. It automatically
     401     * performs the calls to
     402     * {@link #writeStartDataView(XMLStreamWriter, String)} and
     403     * {@link #writeEndDataView(XMLStreamWriter)}.
     404     *
     405     * @param writer
     406     *            the {@link XMLStreamWriter} to be used
     407     * @param text
     408     *            the text content of the hit
     409     * @param hits
     410     *            an even-element array containing tuples for the hit markers in
     411     *            the text content
     412     * @param secondIsLength
     413     *            if <code>true</code> the second element of each tuple in this
     414     *            <code>hits</code> array is interpreted as an length; if
     415     *            <code>false</code> it is interpreted as an end-offset
     416     * @throws XMLStreamException
     417     *             if an error occurred
     418     */
     419    public static void writeHitsDataView(XMLStreamWriter writer, String text,
     420            int[] hits, boolean secondIsLength) throws XMLStreamException {
     421        if (writer == null) {
     422            throw new NullPointerException("writer == null");
     423        }
     424        if (text == null) {
     425            throw new NullPointerException("text == null");
     426        }
     427        if (hits == null) {
     428            throw new NullPointerException("text == null");
     429        }
     430        if ((hits.length == 0) || ((hits.length % 2) != 0)) {
     431            throw new NullPointerException("length of hits array must " +
     432                    "contain an even number of elements");
     433        }
     434
     435        writeStartDataView(writer, FCS_HITS_MIMETYPE);
     436
     437        // actual "hits" data view
     438        writer.setPrefix(FCS_HITS_PREFIX, FCS_HITS_NS);
     439        writer.writeStartElement(FCS_HITS_NS, "Result");
     440        writer.writeNamespace(FCS_HITS_PREFIX, FCS_HITS_NS);
     441
     442        int pos = 0;
     443        for (int i = 0; i < hits.length; i += 2) {
     444            int start  = hits[i];
     445            int end    = hits[i + 1];
     446
     447            if ((start < 0) && (start > text.length())) {
     448                throw new IllegalArgumentException("start index out of " +
     449                        "bounds: start=" + start);
     450            }
     451            if (secondIsLength) {
     452                if (end < 1) {
     453                    throw new IllegalArgumentException(
     454                            "length must be larger than 0: length = " + end);
     455                }
     456                end += start;
     457            }
     458            if (start >= end) {
     459                throw new IllegalArgumentException("end offset must be " +
     460                        "larger then start offset: start=" + start +
     461                        ", end="+ end);
     462            }
     463
     464            if (start > pos) {
     465                String s = text.substring(pos, start);
     466                writer.writeCharacters(s);
     467            }
     468            writer.writeStartElement(FCS_HITS_NS, "Hit");
     469            writer.writeCharacters(text.substring(start, end));
     470            writer.writeEndElement(); // "Hits" element
     471            pos = end;
     472        }
     473        if (pos < text.length() - 1) {
     474            writer.writeCharacters(text.substring(pos));
     475        }
     476
     477        writer.writeEndElement(); // "Result" element
     478
     479        writeEndDataView(writer);
     480    }
     481
     482
     483    /**
     484     * Convince method to write a simple HITS data view. It automatically
     485     * performs the calls to
     486     * {@link #writeStartDataView(XMLStreamWriter, String)} and
     487     * {@link #writeEndDataView(XMLStreamWriter)}.
     488     *
     489     * <pre>
     490     * ...
     491     * writeStartResource(...);
     492     * writeHitsDataView(...);
     493     * writeEndResource(...);
     494     * ...
     495     * </pre>
     496     *
     497     * @param writer
     498     *            the {@link XMLStreamWriter} to be used
     499     * @param pid
     500     *            the persistent identifier of this resource or
     501     *            <code>null</code>, if not applicable
     502     * @param ref
     503     *            the reference of this resource or <code>null</code>, if not
     504     *            applicable
     505     * @param text
     506     *            the text content of the hit
     507     * @param hits
     508     *            an even-element array containing tuples for the hit markers in
     509     *            the text content
     510     * @param secondIsLength
     511     *            if <code>true</code> the second element of each tuple in this
     512     *            <code>hits</code> array is interpreted as an length; if
     513     *            <code>false</code> it is interpreted as an end-offset
     514     * @throws XMLStreamException
     515     *             if an error occurred
     516     */
     517    public static void writeResourceWithHitsDataView(XMLStreamWriter writer,
     518            String pid, String ref, String text,
     519            int[] hits, boolean secondIsLength)
     520            throws XMLStreamException {
     521        if (writer == null) {
     522            throw new NullPointerException("writer == null");
     523        }
     524
     525        writeStartResource(writer, pid, ref);
     526        writeHitsDataView(writer, text, hits, secondIsLength);
     527        writeEndResource(writer);
     528    }
     529
    287530} // class XMLStreamWriterHelper
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/utils/SimpleEndpointDescription.java

    r5485 r5546  
    11package eu.clarin.sru.server.fcs.utils;
    22
     3import java.net.URI;
    34import java.util.Collections;
    45import java.util.List;
    56
    6 import eu.clarin.sru.server.fcs.ResourceInfoInventory;
     7import eu.clarin.sru.server.SRUException;
     8import eu.clarin.sru.server.fcs.DataView;
     9import eu.clarin.sru.server.fcs.EndpointDescription;
    710import eu.clarin.sru.server.fcs.ResourceInfo;
    811
    912
    1013/**
    11  * A very simple resource info inventory that is initialized with a static list
    12  * of resource info records. Mostly used together with
    13  * {@link SimpleResourceInfoInventoryParser}, but it is agnostic how the static
    14  * list of resource info records is generated.
    15  * 
    16  * @see ResourceInfoInventory
    17  * @see SimpleResourceInfoInventoryParser
     14 * A very simple implementation of an endpoint description that is initialized
     15 * from static information supplied at construction time. Mostly used together
     16 * with {@link SimpleEndpointDescriptionParser}, but it is agnostic how the
     17 * static list of resource info records is generated.
     18 *
     19 * @see EndpointDescription
     20 * @see SimpleEndpointDescriptionParser
    1821 */
    19 public class SimpleResourceInfoInventory implements ResourceInfoInventory {
     22public class SimpleEndpointDescription extends AbstractEndpointDescriptionBase {
    2023    private final boolean pidCaseSensitive;
    2124    private final List<ResourceInfo> entries;
     
    2528     * Constructor.
    2629     *
    27      * @param entries
     30     * @param capabilities
     31     *            a list of capabilities supported by this endpoint
     32     * @param supportedDataViews
     33     *            a list of data views that are supported by this endpoint
     34     * @param resources
    2835     *            a static list of resource info records
    2936     * @param pidCaseSensitive
    3037     *            <code>true</code> if comparison of persistent identifiers
    31      *            should be performed case-sensitive, <code>false</code> otherwise
     38     *            should be performed case-sensitive, <code>false</code>
     39     *            otherwise
    3240     */
    33     public SimpleResourceInfoInventory(List<ResourceInfo> entries,
     41    public SimpleEndpointDescription(List<URI> capabilities,
     42            List<DataView> supportedDataViews, List<ResourceInfo> resources,
    3443            boolean pidCaseSensitive) {
    35         if (entries == null) {
     44        super(capabilities, supportedDataViews);
     45
     46        if (resources == null) {
    3647            throw new NullPointerException("entries == null");
    3748        }
    38         this.entries = Collections.unmodifiableList(entries);
     49        this.entries = Collections.unmodifiableList(resources);
    3950        this.pidCaseSensitive = pidCaseSensitive;
    4051    }
     
    4758
    4859    @Override
    49     public List<ResourceInfo> getResourceInfoList(String id) {
    50         if (id == null) {
    51             throw new NullPointerException("id == null");
     60    public List<ResourceInfo> getResourceList(String pid) throws SRUException {
     61        if (pid == null) {
     62            throw new NullPointerException("pid == null");
    5263        }
    53         if (id.isEmpty()) {
    54             throw new IllegalArgumentException("id is empty");
     64        if (pid.isEmpty()) {
     65            throw new IllegalArgumentException("pid is empty");
    5566        }
    5667        if (!pidCaseSensitive) {
    57             id = id.toLowerCase();
     68            pid = pid.toLowerCase();
    5869        }
    59         if (id.equals(PID_ROOT)) {
     70        if (pid.equals(PID_ROOT)) {
    6071            return entries;
    6172        } else {
    62             ResourceInfo ri = findRecursive(entries, id);
     73            ResourceInfo ri = findRecursive(entries, pid);
    6374            if (ri != null) {
    6475                return ri.getSubResources();
     
    93104    }
    94105
    95 } // class SimpleResourceInfoInventory
     106} // class SimpleEndpointDescription
  • FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/utils/SimpleEndpointDescriptionParser.java

    r5485 r5546  
    22
    33import java.io.IOException;
     4import java.net.URI;
     5import java.net.URISyntaxException;
    46import java.net.URL;
    57import java.util.ArrayList;
    6 import java.util.Arrays;
    78import java.util.HashMap;
    89import java.util.HashSet;
    910import java.util.Iterator;
    10 import java.util.LinkedList;
    1111import java.util.List;
    1212import java.util.Map;
     
    3333
    3434import eu.clarin.sru.server.SRUConfigException;
    35 import eu.clarin.sru.server.fcs.ResourceInfoInventory;
     35import eu.clarin.sru.server.fcs.DataView;
     36import eu.clarin.sru.server.fcs.DataView.DeliveryPolicy;
     37import eu.clarin.sru.server.fcs.EndpointDescription;
    3638import eu.clarin.sru.server.fcs.ResourceInfo;
    3739
    3840
    3941/**
    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
     42 * A parser, that parses an XML file and produces a endpoint description with
     43 * static list of resource info records. The XML file has the same format as the
     44 * result format defined for endpoint description of the CLARIN-FCS
     45 * specification. The {@link #parse(URL)} returns a
     46 * {@link SimpleEndpointDescription} instance.
     47 *
     48 * @see EndpointDescription
     49 * @see SimpleEndpointDescription
    4650 */
    47 public class SimpleResourceInfoInventoryParser {
    48     private static final String NS = "http://clarin.eu/fcs/endpoint-description";
     51public class SimpleEndpointDescriptionParser {
     52    private static final String NS =
     53            "http://clarin.eu/fcs/endpoint-description";
     54    private static final String NS_LEGACY =
     55            "http://clarin.eu/fcs/1.0/resource-info";
     56    private static final String CAP_BASIC_SEARCH =
     57            "http://clarin.eu/fcs/capability/basic-search";
    4958    private static final String LANG_EN = "en";
     59    private static final String POLICY_SEND_DEFAULT = "send-by-default";
     60    private static final String POLICY_NEED_REQUEST = "need-to-request";
    5061    private static final Logger logger =
    51             LoggerFactory.getLogger(SimpleResourceInfoInventoryParser.class);
     62            LoggerFactory.getLogger(SimpleEndpointDescriptionParser.class);
    5263
    5364
    5465    /**
    5566     * Parse an XML file and return a static list of resource info records.
    56      * 
     67     *
    5768     * @param url
    5869     *            the URI pointing to the file to be parsed
    59      * @return a list of resource info records represented as
    60      *         {@link ResourceInfo} instances
     70     * @return an {@link EndpointDescription} instance
    6171     * @throws SRUConfigException
    6272     *             if an error occurred
    6373     */
    64     public static ResourceInfoInventory parse(URL url) throws SRUConfigException {
     74    public static EndpointDescription parse(URL url) throws SRUConfigException {
    6575        if (url == null) {
    6676            throw new NullPointerException("url == null");
    6777        }
    6878
    69         logger.debug("parsing resource-info from: {}", url);
    70 
    71         final Set<String> ids = new HashSet<String>();
     79        logger.debug("parsing endpoint description from: {}", url);
     80
    7281        try {
    7382            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     
    7786            Document doc = db.parse(url.openStream());
    7887
    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");
     88            /*
     89             * Detect for deprecated resource-info catalog files and bail, if necessary
     90             */
     91            checkLegacyMode(doc, url);
     92
     93            /*
     94             * Parse on and create endpoint description ...
     95             */
     96            return parseEndpointDescription(doc);
     97        } catch (ParserConfigurationException e) {
     98            throw new SRUConfigException("internal error", e);
     99        } catch (SAXException e) {
     100            throw new SRUConfigException("parsing error", e);
     101        } catch (IOException e) {
     102            throw new SRUConfigException("error reading file", e);
     103        } catch (XPathExpressionException e) {
     104            throw new SRUConfigException("internal error", e);
     105        }
     106    }
     107
     108
     109    private static EndpointDescription parseEndpointDescription(Document doc)
     110            throws SRUConfigException, XPathExpressionException {
     111        XPathFactory factory = XPathFactory.newInstance();
     112        XPath xpath = factory.newXPath();
     113
     114        xpath.setNamespaceContext(new NamespaceContext() {
     115            @Override
     116            public Iterator<?> getPrefixes(String namespaceURI) {
     117                throw new UnsupportedOperationException();
     118            }
     119
     120            @Override
     121            public String getPrefix(String namespaceURI) {
     122                throw new UnsupportedOperationException();
     123            }
     124
     125            @Override
     126            public String getNamespaceURI(String prefix) {
     127                if (prefix == null) {
     128                    throw new NullPointerException("prefix == null");
     129                }
     130                if (prefix.equals("ed")) {
     131                    return NS;
     132                } else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
     133                    return XMLConstants.XML_NS_URI;
     134                } else {
     135                    return XMLConstants.NULL_NS_URI;
     136                }
     137            }
     138        });
     139
     140        // capabilities
     141        List<URI> capabilities = new ArrayList<URI>();
     142        XPathExpression exp1 =
     143                xpath.compile("//ed:Capabilities/ed:Capability");
     144        NodeList list1 = (NodeList) exp1.evaluate(doc, XPathConstants.NODESET);
     145        if ((list1 != null) && (list1.getLength() > 0)) {
     146            logger.debug("parsing capabilities");
     147            for (int i = 0; i < list1.getLength(); i++) {
     148                String s = list1.item(i).getTextContent().trim();
     149                try {
     150                    URI uri = new URI(s);
     151                    if (capabilities.contains(uri)) {
     152                        logger.warn("ignoring duplicate capability " +
     153                                "entry for '{}'", uri);
    96154                    }
    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;
     155                    capabilities.add(uri);
     156                } catch (URISyntaxException e) {
     157                    throw new SRUConfigException("capability is not encoded " +
     158                            "as proper URI: " + s);
     159                }
     160            }
     161        } else {
     162            logger.warn("No capabilities where defined in " +
     163                    "endpoint configuration");
     164        }
     165        URI cap = URI.create(CAP_BASIC_SEARCH);
     166        if (!capabilities.contains(cap)) {
     167            logger.warn("capability '{}' was not defined in endpoint " +
     168                    "description; added it to meet specification. Please " +
     169                    "update your endpoint description!", CAP_BASIC_SEARCH);
     170            capabilities.add(cap);
     171        }
     172        logger.debug("CAPS:'{}'", capabilities);
     173
     174        // supported data views
     175        List<DataView> supportedDataViews = new ArrayList<DataView>();
     176        XPathExpression exp2 =
     177                xpath.compile("//ed:SupportedDataViews/ed:SupportedDataView");
     178        NodeList list2 = (NodeList) exp2.evaluate(doc, XPathConstants.NODESET);
     179        if ((list2 != null) && (list2.getLength() > 0)) {
     180            logger.debug("parsing supported data views");
     181            for (int i = 0; i < list2.getLength(); i++) {
     182                Element item = (Element) list2.item(i);
     183                String id = getAttribute(item, "id");
     184                if (id == null) {
     185                    throw new SRUConfigException("Element <SupportedDataView> "
     186                            + "must carry a proper 'id' attribute");
     187                }
     188                String p = getAttribute(item, "delivery-policy");
     189                if (p == null) {
     190                    throw new SRUConfigException("Element <SupportedDataView> "
     191                            + "must carry a 'delivery-policy' attribute");
     192                }
     193                DeliveryPolicy policy = null;
     194                if (POLICY_SEND_DEFAULT.equals(p)) {
     195                    policy = DeliveryPolicy.SEND_BY_DEFAULT;
     196                } else if (POLICY_NEED_REQUEST.equals(p)) {
     197                    policy = DeliveryPolicy.NEED_TO_REQUEST;
     198                } else {
     199                    throw new SRUConfigException("Invalid value '" + p +
     200                            "' for attribute 'delivery-policy' on element " +
     201                            "<SupportedDataView>");
     202                }
     203                String mimeType = item.getTextContent();
     204                if (mimeType != null) {
     205                    mimeType = mimeType.trim();
     206                    if (mimeType.isEmpty()) {
     207                        mimeType = null;
    103208                    }
    104209                }
    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();
     210                if (mimeType == null) {
     211                    throw new SRUConfigException("Element <SupportedDataView> "
     212                            + "must contain a MIME-type as content");
     213                }
     214                // check for duplicate entries ...
     215                for (DataView dataView : supportedDataViews) {
     216                    if (id.equals(dataView.getIdentifier())) {
     217                        throw new SRUConfigException(
     218                                "A <SupportedDataView> with " + "the id '" +
     219                                        id + "' is already defined!");
     220                    }
     221                    if (mimeType.equals(dataView.getMimeType())) {
     222                        throw new SRUConfigException(
     223                                "A <SupportedDataView> with " +
     224                                        "the MIME-type '" + mimeType +
     225                                        "' is already defined!");
     226                    }
     227                }
     228                supportedDataViews.add(new DataView(id, mimeType, policy));
     229            }
     230        } else {
     231            logger.error("Endpoint configuration contains no valid " +
     232                    "information about supported data views");
     233            throw new SRUConfigException("Endpoint configuration contains " +
     234                    "no valid information about supported data views");
     235        }
     236
     237        logger.debug("DV: {}", supportedDataViews);
     238
     239
     240        // resources
     241        XPathExpression x3 =
     242                xpath.compile("/ed:EndpointDescription/ed:Resources/ed:Resource");
     243        NodeList l3 = (NodeList) x3.evaluate(doc, XPathConstants.NODESET);
     244        final Set<String> ids = new HashSet<String>();
     245        List<ResourceInfo> resources =
     246                parseRessources(xpath, l3, ids, supportedDataViews);
     247        if ((resources == null) || resources.isEmpty()) {
     248            throw new SRUConfigException("No resources where " +
     249                    "defined in endpoint description");
     250        }
     251
     252        return new SimpleEndpointDescription(capabilities,
     253                supportedDataViews,
     254                resources,
     255                false);
     256    }
     257
     258
     259    private static List<ResourceInfo> parseRessources(XPath xpath,
     260            NodeList nodes, Set<String> ids, List<DataView> supportedDataViews)
     261            throws SRUConfigException, XPathExpressionException {
     262      List<ResourceInfo> ris = null;
     263      for (int k = 0; k < nodes.getLength(); k++) {
     264          final Element node                = (Element) nodes.item(k);
     265          String pid                        = null;
     266          Map<String, String> titles        = null;
     267          Map<String, String> descrs        = null;
     268          String link                       = null;
     269          List<String> langs                = null;
     270          List<DataView> availableDataViews = null;
     271          List<ResourceInfo> sub            = null;
     272
     273          pid = getAttribute(node, "pid");
     274          if (pid == null) {
     275              throw new SRUConfigException("Element <ResourceInfo> " +
     276                      "must carry a proper 'pid' attribute");
     277          }
     278          if (ids.contains(pid)) {
     279              throw new SRUConfigException("Another element <Resource> " +
     280                      "with pid '" + pid + "' already exists");
     281          }
     282          ids.add(pid);
     283
     284          XPathExpression x1 = xpath.compile("ed:Title");
     285          NodeList l1 = (NodeList) x1.evaluate(node, XPathConstants.NODESET);
     286          if ((l1 != null) && (l1.getLength() > 0)) {
     287              for (int i = 0; i < l1.getLength(); i++) {
     288                  final Element n = (Element) l1.item(i);
     289
     290                  final String lang = getLangAttribute(n);
     291                  if (lang == null) {
     292                      throw new SRUConfigException("Element <Title> must " +
     293                              "carry a proper 'xml:lang' attribute");
     294                  }
     295
     296                  final String title = cleanString(n.getTextContent());
     297                  if (title == null) {
     298                      throw new SRUConfigException("Element <Title> must " +
     299                              "carry a non-empty 'xml:lang' attribute");
     300                  }
     301
     302                  if (titles == null) {
     303                      titles = new HashMap<String, String>();
     304                  }
     305                  if (titles.containsKey(lang)) {
     306                      logger.warn("title with language '{}' already exists",
     307                              lang);
     308                  } else {
     309                      logger.debug("title: '{}' '{}'", lang, title);
     310                      titles.put(lang, title);
     311                  }
     312              }
     313              if ((titles != null) && !titles.containsKey(LANG_EN)) {
     314                  throw new SRUConfigException(
     315                          "A <Title> with language 'en' is mandatory");
     316              }
     317          }
     318
     319          XPathExpression x2 = xpath.compile("ed:Description");
     320          NodeList l2 = (NodeList) x2.evaluate(node, XPathConstants.NODESET);
     321          if ((l2 != null) && (l2.getLength() > 0)) {
     322              for (int i = 0; i < l2.getLength(); i++) {
     323                  Element n = (Element) l2.item(i);
     324
     325                  String lang = getLangAttribute(n);
     326                  if (lang == null) {
     327                      throw new SRUConfigException("Element <Description> " +
     328                              "must carry a proper 'xml:lang' attribute");
     329
     330                  }
     331                  String desc = cleanString(n.getTextContent());
     332
     333                  if (descrs == null) {
     334                      descrs = new HashMap<String, String>();
     335                  }
     336
     337                  if (descrs.containsKey(lang)) {
     338                      logger.warn("description with language '{}' "
     339                              + "already exists", lang);
     340                  } else {
     341                      logger.debug("description: '{}' '{}'", lang, desc);
     342                      descrs.put(lang, desc);
     343                  }
     344              }
     345              if ((descrs != null) && !descrs.containsKey(LANG_EN)) {
     346                  throw new SRUConfigException(
     347                          "A <Description> with language 'en' is mandatory");
     348              }
     349          }
     350
     351          XPathExpression x3 = xpath.compile("ed:LandingPageURI");
     352          NodeList l3 = (NodeList) x3.evaluate(node, XPathConstants.NODESET);
     353          if ((l3 != null) && (l3.getLength() > 0)) {
     354              for (int i = 0; i < l3.getLength(); i++) {
     355                  Element n = (Element) l3.item(i);
     356                  link = cleanString(n.getTextContent());
     357              }
     358          }
     359
     360          XPathExpression x4 = xpath.compile("ed:Languages/ed:Language");
     361          NodeList l4 = (NodeList) x4.evaluate(node, XPathConstants.NODESET);
     362          if ((l4 != null) && (l4.getLength() > 0)) {
     363              for (int i = 0; i < l4.getLength(); i++) {
     364                  Element n = (Element) l4.item(i);
     365
     366                  String s = n.getTextContent();
     367                  if (s != null) {
     368                      s = s.trim();
     369                      if (s.isEmpty()) {
     370                          s = null;
     371                      }
     372                  }
     373
     374                  /*
     375                   * enforce three letter codes
     376                   */
     377                  if ((s == null) || (s.length() != 3)) {
     378                      throw new SRUConfigException("Element <Language> " +
     379                              "must use ISO-632-3 three letter " +
     380                              "language codes");
     381                  }
     382
     383                  if (langs == null) {
     384                      langs = new ArrayList<String>();
     385                  }
     386                  langs.add(s);
     387              }
     388          }
     389
     390          XPathExpression x5 = xpath.compile("ed:AvailableDataViews");
     391          Node n = (Node) x5.evaluate(node, XPathConstants.NODE);
     392          if ((n != null) && (n instanceof Element)) {
     393              String ref = getAttribute((Element) n, "ref");
     394              if (ref == null) {
     395                  throw new SRUConfigException("Element <AvailableDataViews> " +
     396                          "must carry a 'ref' attribute");
     397              }
     398              String[] refs = ref.split("\\s+");
     399              if ((refs == null) || (refs.length < 1)) {
     400                  throw new SRUConfigException("Attribute 'ref' on element " +
     401                          "<AvailableDataViews> must contain a whitespace " +
     402                          "seperated list of data view references");
     403              }
     404
     405
     406              for (int i = 0; i < refs.length; i++) {
     407                  DataView dataview = null;
     408                  for (DataView dv : supportedDataViews) {
     409                      if (refs[i].equals(dv.getIdentifier())) {
     410                          dataview = dv;
     411                          break;
     412                      }
     413                  }
     414                  if (dataview != null) {
     415                      if (availableDataViews == null) {
     416                          availableDataViews = new ArrayList<DataView>();
     417                      }
     418                      availableDataViews.add(dataview);
     419                  } else {
     420                      throw new SRUConfigException("A data view with " +
     421                              "identifier '" + refs[i] + "' was not defined " +
     422                              "in <SupportedDataViews>");
     423                  }
     424              }
     425          } else {
     426              throw new SRUConfigException(
     427                      "missing element <ed:AvailableDataViews>");
     428          }
     429          if (availableDataViews == null) {
     430              throw new SRUConfigException("No available data views where " +
     431                      "defined for resource with PID '" + pid + "'");
     432          }
     433
     434          XPathExpression x6 = xpath.compile("ed:Resources/ed:Resource");
     435          NodeList l6 = (NodeList) x6.evaluate(node, XPathConstants.NODESET);
     436          if ((l6 != null) && (l6.getLength() > 0)) {
     437              sub = parseRessources(xpath, l6, ids, supportedDataViews);
     438          }
     439
     440          if (ris == null) {
     441              ris = new ArrayList<ResourceInfo>();
     442          }
     443          ris.add(new ResourceInfo(pid,
     444                  titles,
     445                  descrs,
     446                  link,
     447                  langs,
     448                  availableDataViews,
     449                  sub));
     450      }
     451      return ris;
     452    }
     453
     454
     455    private static String getAttribute(Element el, String localName) {
     456        String lang = el.getAttribute(localName);
     457        if (lang != null) {
     458            lang = lang.trim();
     459            if (!lang.isEmpty()) {
     460                return lang;
     461            }
    121462        }
    122463        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;
    293464    }
    294465
     
    328499    }
    329500
     501
     502    private static void checkLegacyMode(Document doc, URL url)
     503            throws SRUConfigException {
     504        Element root = doc.getDocumentElement();
     505        if (root != null) {
     506            String ns = root.getNamespaceURI();
     507            if (ns != null) {
     508                if (ns.equals(NS_LEGACY)) {
     509                    logger.error("Detected out-dated " +
     510                            "resource info catalog file '" + url +
     511                            "'. Please update to the " +
     512                            "current version");
     513                    throw new SRUConfigException("unsupport file format: " + ns);
     514                } else if (!ns.equals(NS)) {
     515                    logger.error("Detected unsupported resource info " +
     516                            "catalog file '" + url + "' with namespace '" + ns + '"');
     517                    throw new SRUConfigException("unsupport file format: " + ns);
     518                }
     519            } else {
     520                throw new SRUConfigException("No namespace URI was detected " +
     521                        "for resource info catalog file '" + url +"'!");
     522            }
     523        } else {
     524            throw new SRUConfigException("Error retrieving root element");
     525        }
     526    }
     527
     528
     529//    private static List<ResourceInfo> parseLegacy(Document doc)
     530//            throws SRUConfigException, XPathExpressionException {
     531
    330532} // class SimpleResourceInfoInventoryParser
Note: See TracChangeset for help on using the changeset viewer.