initial commit

This commit is contained in:
Jörg Prante 2016-11-10 13:30:42 +01:00
commit 3d79ba0650
111 changed files with 6133 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
/data
/work
/logs
/.idea
/target
.DS_Store
*.iml
/.settings
/.classpath
/.project
/.gradle
build
/plugins
/sessions
*~
*.MARC

71
build.gradle Normal file
View file

@ -0,0 +1,71 @@
plugins {
id "org.sonarqube" version "2.2"
id "org.ajoberstar.github-pages" version "1.6.0-rc.1"
id "org.xbib.gradle.plugin.jbake" version "1.1.0"
}
allprojects {
group = 'org.xbib'
version = '1.0.0'
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'findbugs'
apply plugin: 'pmd'
apply plugin: 'checkstyle'
apply plugin: "jacoco"
repositories {
mavenCentral()
}
configurations {
wagon
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.apache.logging.log4j:log4j-core:2.7'
testCompile 'org.apache.logging.log4j:log4j-jul:2.7'
wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10'
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:all" << "-profile" << "compact2"
}
test {
testLogging {
showStandardStreams = false
exceptionFormat = 'full'
}
systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager'
}
task sourcesJar(type: Jar, dependsOn: classes) {
classifier 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier 'javadoc'
}
artifacts {
archives sourcesJar, javadocJar
}
if (project.hasProperty('signing.keyId')) {
signing {
sign configurations.archives
}
}
apply from: "${rootProject.projectDir}/gradle/ext.gradle"
apply from: "${rootProject.projectDir}/gradle/publish.gradle"
apply from: "${rootProject.projectDir}/gradle/sonarqube.gradle"
}

12
gradle/ext.gradle Normal file
View file

@ -0,0 +1,12 @@
ext {
user = 'xbib'
projectName = 'oai'
projectDescription = 'Open Archive Initiative library for Java'
scmUrl = 'https://github.com/xbib/oai'
scmConnection = 'scm:git:git://github.com/xbib/oai.git'
scmDeveloperConnection = 'scm:git:git://github.com/xbib/oai.git'
versions = [
'tcnative': '1.1.33.Fork23',
'alpnboot': '8.1.9.v20160720'
]
}

66
gradle/publish.gradle Normal file
View file

@ -0,0 +1,66 @@
task xbibUpload(type: Upload, dependsOn: build) {
configuration = configurations.archives
uploadDescriptor = true
repositories {
if (project.hasProperty('xbibUsername')) {
mavenDeployer {
configuration = configurations.wagon
repository(url: uri('scpexe://xbib.org/repository')) {
authentication(userName: xbibUsername, privateKey: xbibPrivateKey)
}
}
}
}
}
task sonatypeUpload(type: Upload, dependsOn: build) {
configuration = configurations.archives
uploadDescriptor = true
repositories {
if (project.hasProperty('ossrhUsername')) {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: uri(ossrhReleaseUrl)) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: uri(ossrhSnapshotUrl)) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
groupId project.group
artifactId project.name
version project.version
name project.name
description projectDescription
packaging 'jar'
inceptionYear '2016'
url scmUrl
organization {
name 'xbib'
url 'http://xbib.org'
}
developers {
developer {
id user
name 'Jörg Prante'
email 'joergprante@gmail.com'
url 'https://github.com/jprante'
}
}
scm {
url scmUrl
connection scmConnection
developerConnection scmDeveloperConnection
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
}
}
}
}

41
gradle/sonarqube.gradle Normal file
View file

@ -0,0 +1,41 @@
tasks.withType(FindBugs) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = false
}
}
tasks.withType(Pmd) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = true
}
}
tasks.withType(Checkstyle) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = true
}
}
jacocoTestReport {
reports {
xml.enabled true
csv.enabled false
xml.destination "${buildDir}/reports/jacoco-xml"
html.destination "${buildDir}/reports/jacoco-html"
}
}
sonarqube {
properties {
property "sonar.projectName", "${project.group} ${project.name}"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.tests", "src/test/java"
property "sonar.scm.provider", "git"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.junit.reportsPath", "build/test-results/test/"
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,6 @@
#Mon Oct 03 00:03:03 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip

33
oai-client/build.gradle Normal file
View file

@ -0,0 +1,33 @@
configurations {
alpnboot
}
dependencies {
compile project(':oai-common')
compile "org.xbib.helianthus:helianthus-client:1.0.3"
if ('os x' == org.gradle.internal.os.OperatingSystem.current().getFamilyName()) {
testCompile "io.netty:netty-tcnative:${versions.tcnative}:osx-x86_64"
}
if ('linux' == org.gradle.internal.os.OperatingSystem.current().getFamilyName()) {
if (new File("/etc/redhat-release").exists()) {
// use this for linking to libssl.so.10 (RHEL/Fedora/CentOS)
testCompile "io.netty:netty-tcnative:${versions.tcnative}:linux-x86_64-fedora"
} else {
// use this for linking to libssl.so.1.0.0
testCompile "io.netty:netty-tcnative:${versions.tcnative}:linux-x86_64"
}
}
alpnboot "org.mortbay.jetty.alpn:alpn-boot:${versions.alpnboot}"
}
test {
testLogging {
showStandardStreams = true
exceptionFormat = 'full'
}
// note: bootstrapClasspath does not use /p (or /a)
jvmArgs "-Xbootclasspath/p:" + configurations.alpnboot.asPath
systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager'
systemProperty 'io.netty.leakDetection.level', 'advanced'
}

View file

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<property name="groups" value="com,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>

View file

@ -0,0 +1,174 @@
package org.xbib.oai.client;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.OAIRequest;
import org.xbib.oai.util.ResumptionToken;
import org.xbib.oai.util.URIBuilder;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
/**
* Client OAI request
*/
public class ClientOAIRequest implements OAIRequest {
private URIBuilder uriBuilder;
private DateTimeFormatter dateTimeFormatter;
private ResumptionToken<?> token;
private String set;
private String metadataPrefix;
private Instant from;
private Instant until;
private boolean retry;
protected ClientOAIRequest() {
uriBuilder = new URIBuilder();
}
public void setURL(URL url) {
try {
URI uri = url.toURI();
uriBuilder.scheme(uri.getScheme())
.authority(uri.getAuthority())
.path(uri.getPath());
} catch (URISyntaxException e) {
throw new IllegalArgumentException("invalid URI " + url);
}
}
public URL getURL() throws MalformedURLException {
return uriBuilder.build().toURL();
}
public String getPath() {
return uriBuilder.buildGetPath();
}
public void addParameter(String name, String value) {
if (value != null && !value.isEmpty()) {
uriBuilder.addParameter(name, value);
}
}
@Override
public void setSet(String set) {
this.set = set;
addParameter(OAIConstants.SET_PARAMETER, set);
}
public String getSet() {
return set;
}
@Override
public void setMetadataPrefix(String prefix) {
this.metadataPrefix = prefix;
addParameter(OAIConstants.METADATA_PREFIX_PARAMETER, prefix);
}
public String getMetadataPrefix() {
return metadataPrefix;
}
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
@Override
public void setFrom(Instant from) {
this.from = from;
String fromStr = dateTimeFormatter == null ? from.toString() : dateTimeFormatter.format(from);
addParameter(OAIConstants.FROM_PARAMETER, fromStr);
}
public Instant getFrom() {
return from;
}
@Override
public void setUntil(Instant until) {
this.until = until;
String untilStr = dateTimeFormatter == null ? until.toString() : dateTimeFormatter.format(until);
addParameter(OAIConstants.UNTIL_PARAMETER, untilStr);
}
public Instant getUntil() {
return until;
}
public void setResumptionToken(ResumptionToken<?> token) {
this.token = token;
if (token != null && token.toString() != null) {
// resumption token may have characters that are illegal in URIs like '|'
//String tokenStr = URIFormatter.encode(token.toString(), StandardCharsets.UTF_8);
addParameter(OAIConstants.RESUMPTION_TOKEN_PARAMETER, token.toString());
}
}
public ResumptionToken<?> getResumptionToken() {
return token;
}
public void setRetry(boolean retry) {
this.retry = retry;
}
public boolean isRetry() {
return retry;
}
class GetRecord extends ClientOAIRequest {
public GetRecord() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.GET_RECORD);
}
}
class Identify extends ClientOAIRequest {
public Identify() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.IDENTIFY);
}
}
class ListIdentifiers extends ClientOAIRequest {
public ListIdentifiers() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.LIST_IDENTIFIERS);
}
}
class ListMetadataFormats extends ClientOAIRequest {
public ListMetadataFormats() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.LIST_METADATA_FORMATS);
}
}
class ListRecordsRequest extends ClientOAIRequest {
public ListRecordsRequest() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.LIST_RECORDS);
}
}
class ListSetsRequest extends ClientOAIRequest {
public ListSetsRequest() {
addParameter(OAIConstants.VERB_PARAMETER, OAIConstants.LIST_SETS);
}
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.oai.client;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.OAIResponse;
import java.io.IOException;
import java.io.Writer;
/**
* Default OAI response
*/
public interface ClientOAIResponse extends OAIResponse {
void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException;
}

View file

@ -0,0 +1,191 @@
package org.xbib.oai.client;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Duration;
import org.xbib.helianthus.client.ClientBuilder;
import org.xbib.helianthus.client.ClientFactory;
import org.xbib.helianthus.client.http.HttpClient;
import org.xbib.oai.client.getrecord.GetRecordRequest;
import org.xbib.oai.client.identify.IdentifyRequest;
import org.xbib.oai.client.listidentifiers.ListIdentifiersRequest;
import org.xbib.oai.client.listmetadataformats.ListMetadataFormatsRequest;
import org.xbib.oai.client.listrecords.ListRecordsRequest;
import org.xbib.oai.client.listsets.ListSetsRequest;
import org.xbib.oai.util.ResumptionToken;
/**
* Default OAI client
*/
public class DefaultOAIClient implements OAIClient {
private HttpClient client;
private ClientFactory clientFactory;
private URL url;
@Override
public DefaultOAIClient setURL(URL url) throws URISyntaxException {
return setURL(url, false);
}
@Override
public DefaultOAIClient setURL(URL url, boolean trustAlways) throws URISyntaxException {
this.url = url;
this.clientFactory = ClientFactory.DEFAULT;
this.client = new ClientBuilder("none+" + url.toURI())
.factory(clientFactory)
.defaultResponseTimeout(Duration.ofMinutes(1L)) // maybe not enough for extreme slow archive servers...
.build(HttpClient.class);
return this;
}
@Override
public URL getURL() {
return url;
}
@Override
public HttpClient getHttpClient() {
return client;
}
@Override
public ClientFactory getFactory() {
return clientFactory;
}
@Override
public IdentifyRequest newIdentifyRequest() {
IdentifyRequest request = new IdentifyRequest();
request.setURL(url);
return request;
}
@Override
public ListMetadataFormatsRequest newListMetadataFormatsRequest() {
ListMetadataFormatsRequest request = new ListMetadataFormatsRequest();
request.setURL(getURL());
return request;
}
@Override
public ListSetsRequest newListSetsRequest() {
ListSetsRequest request = new ListSetsRequest();
request.setURL(getURL());
return request;
}
@Override
public ListIdentifiersRequest newListIdentifiersRequest() {
ListIdentifiersRequest request = new ListIdentifiersRequest();
request.setURL(getURL());
return request;
}
@Override
public GetRecordRequest newGetRecordRequest() {
GetRecordRequest request = new GetRecordRequest();
request.setURL(getURL());
return request;
}
@Override
public ListRecordsRequest newListRecordsRequest() {
ListRecordsRequest request = new ListRecordsRequest();
request.setURL(getURL());
return request;
}
@Override
public IdentifyRequest resume(IdentifyRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newIdentifyRequest();
request.setResumptionToken(token);
return request;
}
@Override
public ListRecordsRequest resume(ListRecordsRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newListRecordsRequest();
request.setResumptionToken(token);
return request;
}
@Override
public ListIdentifiersRequest resume(ListIdentifiersRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newListIdentifiersRequest();
request.setResumptionToken(token);
return request;
}
@Override
public ListMetadataFormatsRequest resume(ListMetadataFormatsRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newListMetadataFormatsRequest();
request.setResumptionToken(token);
return request;
}
@Override
public ListSetsRequest resume(ListSetsRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newListSetsRequest();
request.setResumptionToken(token);
return request;
}
@Override
public GetRecordRequest resume(GetRecordRequest request, ResumptionToken<?> token) {
if (request.isRetry()) {
request.setRetry(false);
return request;
}
if (token == null) {
return null;
}
request = newGetRecordRequest();
request.setResumptionToken(token);
return request;
}
@Override
public void close() throws IOException {
}
}

View file

@ -0,0 +1,107 @@
package org.xbib.oai.client;
import java.net.URISyntaxException;
import java.net.URL;
import org.xbib.helianthus.client.ClientFactory;
import org.xbib.helianthus.client.http.HttpClient;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.OAISession;
import org.xbib.oai.client.getrecord.GetRecordRequest;
import org.xbib.oai.client.identify.IdentifyRequest;
import org.xbib.oai.client.listidentifiers.ListIdentifiersRequest;
import org.xbib.oai.client.listmetadataformats.ListMetadataFormatsRequest;
import org.xbib.oai.client.listrecords.ListRecordsRequest;
import org.xbib.oai.client.listsets.ListSetsRequest;
import org.xbib.oai.util.ResumptionToken;
/**
* OAI client API
*
*/
public interface OAIClient extends OAISession, OAIConstants {
OAIClient setURL(URL uri, boolean trustAlways) throws URISyntaxException;
OAIClient setURL(URL uri) throws URISyntaxException;
URL getURL();
HttpClient getHttpClient();
ClientFactory getFactory();
/**
* This verb is used to retrieve information about a repository.
* Some of the information returned is required as part of the OAI-PMH.
* Repositories may also employ the Identify verb to return additional
* descriptive information.
* @return identify request
*/
IdentifyRequest newIdentifyRequest();
IdentifyRequest resume(IdentifyRequest request, ResumptionToken<?> token);
/**
* This verb is an abbreviated form of ListRecords, retrieving only
* headers rather than records. Optional arguments permit selective
* harvesting of headers based on set membership and/or datestamp.
* Depending on the repository's support for deletions, a returned
* header may have a status attribute of "deleted" if a record
* matching the arguments specified in the request has been deleted.
* @return list identifiers request
*
*/
ListIdentifiersRequest newListIdentifiersRequest();
ListIdentifiersRequest resume(ListIdentifiersRequest request, ResumptionToken<?> token);
/**
* This verb is used to retrieve the metadata formats available
* from a repository. An optional argument restricts the request
* to the formats available for a specific item.
* @return list metadata formats request
*/
ListMetadataFormatsRequest newListMetadataFormatsRequest();
ListMetadataFormatsRequest resume(ListMetadataFormatsRequest request, ResumptionToken<?> token);
/**
* This verb is used to retrieve the set structure of a repository,
* useful for selective harvesting.
* @return list sets request
*/
ListSetsRequest newListSetsRequest();
ListSetsRequest resume(ListSetsRequest request, ResumptionToken<?> token);
/**
* This verb is used to harvest records from a repository.
* Optional arguments permit selective harvesting of records based on
* set membership and/or datestamp. Depending on the repository's
* support for deletions, a returned header may have a status
* attribute of "deleted" if a record matching the arguments
* specified in the request has been deleted. No metadata
* will be present for records with deleted status.
* @return list records request
*/
ListRecordsRequest newListRecordsRequest();
ListRecordsRequest resume(ListRecordsRequest request, ResumptionToken<?> token);
/**
* This verb is used to retrieve an individual metadata record from
* a repository. Required arguments specify the identifier of the item
* from which the record is requested and the format of the metadata
* that should be included in the record. Depending on the level at
* which a repository tracks deletions, a header with a "deleted" value
* for the status attribute may be returned, in case the metadata format
* specified by the metadataPrefix is no longer available from the
* repository or from the specified item.
* @return get record request
*/
GetRecordRequest newGetRecordRequest();
GetRecordRequest resume(GetRecordRequest request, ResumptionToken<?> token);
}

View file

@ -0,0 +1,58 @@
package org.xbib.oai.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
/**
* Factory for OAI clients
*
*/
public class OAIClientFactory {
private final static OAIClientFactory instance = new OAIClientFactory();
private OAIClientFactory() {
}
public static OAIClientFactory getInstance() {
return instance;
}
public static OAIClient newClient() {
return new DefaultOAIClient();
}
public static OAIClient newClient(String spec) {
return newClient(spec, false);
}
public static OAIClient newClient(String spec, boolean trustAll) {
Properties properties = new Properties();
InputStream in = instance.getClass().getResourceAsStream("/org/xbib/oai/client/" + spec + ".properties");
if (in != null) {
try {
properties.load(in);
} catch (IOException ex) {
// ignore
}
DefaultOAIClient client = new DefaultOAIClient();
try {
client.setURL(new URL(properties.getProperty("uri")), trustAll);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
return client;
} else {
DefaultOAIClient client = new DefaultOAIClient();
try {
client.setURL(new URL(spec));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
return client;
}
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.oai.client.getrecord;
import org.xbib.oai.client.ClientOAIRequest;
/**
*
*/
public class GetRecordRequest extends ClientOAIRequest {
public GetRecordRequest() {
super();
addParameter(VERB_PARAMETER, GET_RECORD);
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.oai.client.getrecord;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import java.io.IOException;
import java.io.Writer;
/**
*
*/
public class GetRecordResponse implements ClientOAIResponse {
@Override
public void to(Writer writer) throws IOException {
}
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI get record verb.
*/
package org.xbib.oai.client.getrecord;

View file

@ -0,0 +1,15 @@
package org.xbib.oai.client.identify;
import org.xbib.oai.client.ClientOAIRequest;
import org.xbib.oai.OAIRequest;
/**
*
*/
public class IdentifyRequest extends ClientOAIRequest implements OAIRequest {
public IdentifyRequest() {
super();
addParameter(VERB_PARAMETER, IDENTIFY);
}
}

View file

@ -0,0 +1,134 @@
package org.xbib.oai.client.identify;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
*
*/
public class IdentifyResponse implements ClientOAIResponse {
private String repositoryName;
private URL baseURL;
private String protocolVersion;
private List<String> adminEmails = new ArrayList<>();
private Date earliestDatestamp;
private String deletedRecord;
private String granularity;
private String compression;
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(message.content().toStringUtf8()));
Document doc = db.parse(is);
setGranularity(getString("granularity", doc.getDocumentElement()));
} catch (ParserConfigurationException | SAXException e) {
throw new IOException(e);
}
}
@Override
public void to(Writer writer) throws IOException {
}
public void setRepositoryName(String repositoryName) {
this.repositoryName = repositoryName;
}
public String getRepositoryName() {
return repositoryName;
}
public void setBaseURL(URL url) {
this.baseURL = url;
}
public URL getBaseURL() {
return baseURL;
}
public void setProtocolVersion(String protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getProtocolVersion() {
return protocolVersion;
}
public void addAdminEmail(String email) {
adminEmails.add(email);
}
public List<String> getAdminEmails() {
return adminEmails;
}
public void setEarliestDatestamp(Date earliestDatestamp) {
this.earliestDatestamp = earliestDatestamp;
}
public Date getEarliestDatestamp() {
return earliestDatestamp;
}
public void setDeletedRecord(String deletedRecord) {
this.deletedRecord = deletedRecord;
}
public String getDeleteRecord() {
return deletedRecord;
}
public void setGranularity(String granularity) {
this.granularity = granularity;
}
public String getGranularity() {
return granularity;
}
public void setCompression(String compression) {
this.compression = compression;
}
public String getCompression() {
return compression;
}
private String getString(String tagName, Element element) {
NodeList list = element.getElementsByTagName(tagName);
if (list != null && list.getLength() > 0) {
NodeList subList = list.item(0).getChildNodes();
if (subList != null && subList.getLength() > 0) {
return subList.item(0).getNodeValue();
}
}
return null;
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI identify verb.
*/
package org.xbib.oai.client.identify;

View file

@ -0,0 +1,15 @@
package org.xbib.oai.client.listidentifiers;
import org.xbib.oai.client.ClientOAIRequest;
import org.xbib.oai.OAIRequest;
/**
*
*/
public class ListIdentifiersRequest extends ClientOAIRequest implements OAIRequest {
public ListIdentifiersRequest() {
super();
addParameter(VERB_PARAMETER, LIST_IDENTIFIERS);
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.oai.client.listidentifiers;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import java.io.IOException;
import java.io.Writer;
/**
*
*/
public class ListIdentifiersResponse implements ClientOAIResponse {
@Override
public void to(Writer writer) throws IOException {
}
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI list identifiers verb.
*/
package org.xbib.oai.client.listidentifiers;

View file

@ -0,0 +1,16 @@
package org.xbib.oai.client.listmetadataformats;
import org.xbib.oai.client.ClientOAIRequest;
import org.xbib.oai.OAIRequest;
/**
*
*/
public class ListMetadataFormatsRequest extends ClientOAIRequest implements OAIRequest {
public ListMetadataFormatsRequest() {
super();
addParameter(VERB_PARAMETER, LIST_METADATA_FORMATS);
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.oai.client.listmetadataformats;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import java.io.IOException;
import java.io.Writer;
/**
*
*/
public class ListMetadataFormatsResponse implements ClientOAIResponse {
@Override
public void to(Writer writer) throws IOException {
}
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI list metadata formats verb.
*/
package org.xbib.oai.client.listmetadataformats;

View file

@ -0,0 +1,215 @@
package org.xbib.oai.client.listrecords;
import org.xbib.content.xml.util.XMLFilterReader;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.util.RecordHeader;
import org.xbib.oai.util.ResumptionToken;
import org.xbib.oai.xml.MetadataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class ListRecordsFilterReader extends XMLFilterReader {
private static final Logger logger = Logger.getLogger(ListRecordsFilterReader.class.getName());
private final ListRecordsRequest request;
private final ListRecordsResponse response;
private StringBuilder content;
private RecordHeader header;
private ResumptionToken<String> token;
private boolean inMetadata;
ListRecordsFilterReader(ListRecordsRequest request, ListRecordsResponse response) {
super();
this.request = request;
this.response = response;
this.content = new StringBuilder();
this.inMetadata = false;
}
public ResumptionToken<String> getResumptionToken() {
return token;
}
public ListRecordsResponse getResponse() {
return response;
}
@Override
public void startDocument() throws SAXException {
logger.log(Level.FINE, "start of document");
super.startDocument();
request.setResumptionToken(null);
}
@Override
public void endDocument() throws SAXException {
logger.log(Level.FINE, "end of document");
super.endDocument();
}
@Override
public void startElement(String uri, String localname, String qname, Attributes atts) throws SAXException {
super.startElement(uri, localname, qname, atts);
if (OAIConstants.NS_URI.equals(uri)) {
switch (localname) {
case "header":
header = new RecordHeader();
break;
case "error":
response.setError(atts.getValue("code"));
break;
case "metadata":
inMetadata = true;
for (MetadataHandler mh : request.getHandlers()) {
mh.startDocument();
}
break;
case "resumptionToken":
try {
token = ResumptionToken.newToken(null);
String cursor = atts.getValue("cursor");
if (cursor != null) {
token.setCursor(Integer.parseInt(cursor));
}
String completeListSize = atts.getValue("completeListSize");
if (completeListSize != null) {
token.setCompleteListSize(Integer.parseInt(completeListSize));
}
if (!token.isComplete()) {
request.setResumptionToken(token);
}
} catch (Exception e) {
throw new SAXException(e);
}
break;
}
return;
}
if (inMetadata) {
for (MetadataHandler mh : request.getHandlers()) {
mh.startElement(uri, localname, qname, atts);
}
}
}
@Override
public void endElement(String nsURI, String localname, String qname) throws SAXException {
super.endElement(nsURI, localname, qname);
if (OAIConstants.NS_URI.equals(nsURI)) {
switch (localname) {
case "header":
for (MetadataHandler mh : request.getHandlers()) {
mh.setHeader(header);
}
header = new RecordHeader();
break;
case "metadata":
for (MetadataHandler mh : request.getHandlers()) {
mh.endDocument();
}
inMetadata = false;
break;
case "responseDate":
response.setDate(Instant.parse(content.toString().trim()));
break;
case "resumptionToken":
if (token != null && content != null && content.length() > 0) {
token.setValue(content.toString());
// feedback to request
request.setResumptionToken(token);
} else {
logger.log(Level.WARNING, "empty resumption token value");
// some servers send a null or an empty token as last token
token = null;
request.setResumptionToken(null);
}
break;
case "identifier":
if (header != null && content != null && content.length() > 0) {
String id = content.toString().trim();
header.setIdentifier(id);
}
break;
case "datestamp":
if (header != null && content != null && content.length() > 0) {
try {
header.setDate(Instant.parse(content.toString().trim()));
} catch (DateTimeParseException e) {
// not "seconds ISO"
}
try {
LocalDateTime ldt = LocalDateTime.parse(content.toString().trim(),
DateTimeFormatter.ofPattern("yyyy-MM-dd"));
header.setDate(Instant.from(ldt));
} catch (DateTimeParseException e) {
// not "day ISO"
}
}
break;
case "setSpec":
if (header != null && content != null && content.length() > 0) {
header.setSetspec(content.toString().trim());
}
break;
}
if (content != null) {
content.setLength(0);
}
return;
}
if (inMetadata) {
for (MetadataHandler mh : request.getHandlers()) {
mh.endElement(nsURI, localname, qname);
}
}
content.setLength(0);
}
@Override
public void characters(char[] chars, int start, int length) throws SAXException {
super.characters(chars, start, length);
content.append(new String(chars, start, length).trim());
if (inMetadata) {
for (MetadataHandler mh : request.getHandlers()) {
mh.characters(chars, start, length);
}
}
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
super.startPrefixMapping(prefix, uri);
if (inMetadata) {
for (MetadataHandler mh : request.getHandlers()) {
mh.startPrefixMapping(prefix, uri);
}
}
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
super.endPrefixMapping(prefix);
if (inMetadata) {
for (MetadataHandler mh : request.getHandlers()) {
mh.endPrefixMapping(prefix);
}
}
}
}

View file

@ -0,0 +1,28 @@
package org.xbib.oai.client.listrecords;
import org.xbib.oai.client.ClientOAIRequest;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.xml.MetadataHandler;
import java.util.LinkedList;
import java.util.List;
/**
*
*/
public class ListRecordsRequest extends ClientOAIRequest {
private List<MetadataHandler> handlers = new LinkedList<>();
public ListRecordsRequest() {
super();
addParameter(OAIConstants.VERB_PARAMETER, LIST_RECORDS);
}
public ListRecordsRequest addHandler(MetadataHandler handler) {
handlers.add(handler);
return this;
}
public List<MetadataHandler> getHandlers() { return handlers; }
}

View file

@ -0,0 +1,163 @@
package org.xbib.oai.client.listrecords;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.util.AsciiString;
import org.xbib.content.xml.transform.TransformerURIResolver;
import org.xbib.content.xml.util.XMLUtil;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import org.xbib.oai.exceptions.BadArgumentException;
import org.xbib.oai.exceptions.BadResumptionTokenException;
import org.xbib.oai.exceptions.NoRecordsMatchException;
import org.xbib.oai.exceptions.OAIException;
import org.xbib.oai.util.ResumptionToken;
import org.xml.sax.InputSource;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class ListRecordsResponse implements ClientOAIResponse {
private static final Logger logger = Logger.getLogger(ListRecordsResponse.class.getName());
private static final String[] RETRY_AFTER_HEADERS = {
"retry-after", "Retry-after", "Retry-After"
};
private final ListRecordsRequest request;
private ListRecordsFilterReader filterreader;
private long retryAfterMillis;
private String error;
private Instant date;
public ListRecordsResponse(ListRecordsRequest request) {
this.request = request;
this.retryAfterMillis = 20 * 1000; // 20 seconds by default
}
public ListRecordsResponse setRetryAfter(long millis) {
this.retryAfterMillis = millis;
return this;
}
public void setError(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setDate(Instant date) {
this.date = date;
}
public Instant getDate() {
return date;
}
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
String content = message.content().toStringUtf8();
int status = message.status().code();
if (status == 503) {
long secs = retryAfterMillis / 1000;
if (message.headers() != null) {
for (String retryAfterHeader : RETRY_AFTER_HEADERS) {
String retryAfter = message.headers().get(AsciiString.of(retryAfterHeader));
if (retryAfter == null) {
continue;
}
secs = Long.parseLong(retryAfter);
if (!isDigits(retryAfter)) {
// parse RFC date, e.g. Fri, 31 Dec 1999 23:59:59 GMT
Instant instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(retryAfter));
secs = ChronoUnit.SECONDS.between(instant, Instant.now());
logger.log(Level.INFO, MessageFormat.format("parsed delay seconds is {0}", secs));
}
logger.log(Level.INFO, MessageFormat.format("setting delay seconds to {0}", secs));
}
}
request.setRetry(true);
try {
if (secs > 0L) {
logger.log(Level.INFO, MessageFormat.format("waiting for {0} seconds (retry-after)", secs));
Thread.sleep(1000 * secs);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.log(Level.SEVERE, "interrupted");
}
return;
}
if (status != 200) {
throw new IOException("status = " + status + " response = " + content);
}
// activate XSLT only if OAI XML content type is returned
String contentType = message.headers().get(HttpHeaderNames.CONTENT_TYPE);
if (contentType != null && !contentType.startsWith("text/xml")) {
throw new IOException("no XML content type in response: " + contentType);
}
this.filterreader = new ListRecordsFilterReader(request, this);
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setURIResolver(new TransformerURIResolver("xsl"));
Transformer transformer = transformerFactory.newTransformer();
Source source = new SAXSource(filterreader, new InputSource(new StringReader(XMLUtil.sanitize(content))));
StreamResult streamResult = new StreamResult(writer);
logger.log(Level.FINE, "transforming");
transformer.transform(source, streamResult);
if ("noRecordsMatch".equals(error)) {
throw new NoRecordsMatchException("metadataPrefix=" + request.getMetadataPrefix()
+ ",set=" + request.getSet()
+ ",from=" + request.getFrom()
+ ",until=" + request.getUntil());
} else if ("badResumptionToken".equals(error)) {
throw new BadResumptionTokenException(request.getResumptionToken());
} else if ("badArgument".equals(error)) {
throw new BadArgumentException();
} else if (error != null) {
throw new OAIException(error);
}
} catch (TransformerException t) {
throw new IOException(t);
}
}
@Override
public void to(Writer writer) throws IOException {
}
private boolean isDigits(String str) {
for (int i = 0; i < str.length(); i++) {
if (!Character.isDigit(str.charAt(i))) {
return false;
}
}
return true;
}
public ResumptionToken<?> getResumptionToken() {
return filterreader != null ? filterreader.getResumptionToken() : null;
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI list records verb.
*/
package org.xbib.oai.client.listrecords;

View file

@ -0,0 +1,15 @@
package org.xbib.oai.client.listsets;
import org.xbib.oai.client.ClientOAIRequest;
/**
*
*/
public class ListSetsRequest extends ClientOAIRequest {
public ListSetsRequest() {
super();
addParameter(VERB_PARAMETER, LIST_SETS);
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.oai.client.listsets;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.oai.client.ClientOAIResponse;
import java.io.IOException;
import java.io.Writer;
/**
*
*/
public class ListSetsResponse implements ClientOAIResponse {
@Override
public void to(Writer writer) throws IOException {
}
@Override
public void receivedResponse(AggregatedHttpMessage message, Writer writer) throws IOException {
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI list sets verb.
*/
package org.xbib.oai.client.listsets;

View file

@ -0,0 +1,4 @@
/**
* Classes for OAI client.
*/
package org.xbib.oai.client;

View file

@ -0,0 +1,124 @@
package org.xbib.oai.client;
import static org.junit.Assert.assertTrue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.xbib.helianthus.client.http.HttpClient;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.helianthus.common.http.HttpHeaderNames;
import org.xbib.helianthus.common.http.HttpHeaders;
import org.xbib.helianthus.common.http.HttpMethod;
import org.xbib.oai.client.identify.IdentifyRequest;
import org.xbib.oai.client.identify.IdentifyResponse;
import org.xbib.oai.client.listrecords.ListRecordsRequest;
import org.xbib.oai.client.listrecords.ListRecordsResponse;
import org.xbib.oai.xml.SimpleMetadataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.ConnectException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
/**
*
*/
public class ArxivClientTest {
private static final Logger logger = LogManager.getLogger(ArxivClientTest.class.getName());
@Test
public void testListRecordsArxiv() throws Exception {
try {
OAIClient client = OAIClientFactory.newClient("http://export.arxiv.org/oai2");
IdentifyRequest identifyRequest = client.newIdentifyRequest();
HttpClient httpClient = client.getHttpClient();
AggregatedHttpMessage response = httpClient.execute(HttpHeaders.of(HttpMethod.GET, identifyRequest.getPath())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
IdentifyResponse identifyResponse = new IdentifyResponse();
identifyResponse.receivedResponse(response, new StringWriter());
String granularity = identifyResponse.getGranularity();
logger.info("granularity = {}", granularity);
DateTimeFormatter dateTimeFormatter = "YYYY-MM-DD".equals(granularity) ?
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("GMT")) : null;
// ArXiv wants us to wait 20 secs between *every* HTTP request, so we must wait here
logger.info("waiting 20 seconds");
Thread.sleep(20 * 1000L);
ListRecordsRequest listRecordsRequest = client.newListRecordsRequest();
listRecordsRequest.setDateTimeFormatter(dateTimeFormatter);
listRecordsRequest.setFrom(Instant.parse("2016-11-01T00:00:00Z"));
listRecordsRequest.setUntil(Instant.parse("2016-11-02T00:00:00Z"));
listRecordsRequest.setMetadataPrefix("arXiv");
final AtomicLong count = new AtomicLong(0L);
SimpleMetadataHandler simpleMetadataHandler = new SimpleMetadataHandler() {
@Override
public void startDocument() throws SAXException {
logger.debug("start doc");
}
@Override
public void endDocument() throws SAXException {
logger.debug("end doc");
count.incrementAndGet();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void startElement(String ns, String localname, String qname, Attributes atrbts) throws SAXException {
}
@Override
public void endElement(String ns, String localname, String qname) throws SAXException {
}
@Override
public void characters(char[] chars, int pos, int len) throws SAXException {
}
};
File file = File.createTempFile("arxiv.", ".xml");
file.deleteOnExit();
FileWriter fileWriter = new FileWriter(file);
while (listRecordsRequest != null) {
try {
listRecordsRequest.addHandler(simpleMetadataHandler);
ListRecordsResponse listRecordsResponse = new ListRecordsResponse(listRecordsRequest);
logger.info("sending {}", listRecordsRequest.getPath());
response = httpClient.execute(HttpHeaders.of(HttpMethod.GET, listRecordsRequest.getPath())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
logger.debug("response headers = {} resumption-token = {}",
response.headers(), listRecordsResponse.getResumptionToken());
listRecordsResponse.receivedResponse(response, fileWriter);
listRecordsRequest = client.resume(listRecordsRequest, listRecordsResponse.getResumptionToken());
} catch (IOException e) {
logger.error(e.getMessage(), e);
listRecordsRequest = null;
}
}
fileWriter.close();
client.close();
logger.info("count={}", count.get());
assertTrue(count.get() > 0L);
} catch (ConnectException | ExecutionException e) {
logger.warn("skipped, can not connect", e);
} catch (InterruptedException | IOException e) {
throw e;
}
}
}

View file

@ -0,0 +1,115 @@
package org.xbib.oai.client;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.ConnectException;
import java.time.Instant;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.xbib.helianthus.client.http.HttpClient;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.helianthus.common.http.HttpHeaderNames;
import org.xbib.helianthus.common.http.HttpHeaders;
import org.xbib.helianthus.common.http.HttpMethod;
import org.xbib.oai.client.identify.IdentifyRequest;
import org.xbib.oai.client.listrecords.ListRecordsRequest;
import org.xbib.oai.client.listrecords.ListRecordsResponse;
import org.xbib.oai.xml.SimpleMetadataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import static org.junit.Assert.assertEquals;
/**
*
*/
public class DNBClientTest {
private static final Logger logger = LogManager.getLogger(DNBClientTest.class.getName());
@Test
public void testIdentify() throws Exception {
OAIClient client = OAIClientFactory.newClient("http://services.dnb.de/oai/repository");
IdentifyRequest request = client.newIdentifyRequest();
HttpClient httpClient = client.getHttpClient();
assertEquals("/oai/repository?verb=Identify", request.getPath());
AggregatedHttpMessage response = httpClient.get(request.getPath()).aggregate().get();
logger.info("{}", response.content().toStringUtf8());
}
@Test
public void testListRecordsDNB() throws Exception {
try {
OAIClient client = OAIClientFactory.newClient("http://services.dnb.de/oai/repository");
ListRecordsRequest listRecordsRequest = client.newListRecordsRequest();
listRecordsRequest.setFrom(Instant.parse("2016-01-01T00:00:00Z"));
listRecordsRequest.setUntil(Instant.parse("2016-01-10T00:00:00Z"));
listRecordsRequest.setSet("bib");
listRecordsRequest.setMetadataPrefix("PicaPlus-xml");
final AtomicLong count = new AtomicLong(0L);
SimpleMetadataHandler simpleMetadataHandler = new SimpleMetadataHandler() {
@Override
public void startDocument() throws SAXException {
logger.debug("startDocument");
}
@Override
public void endDocument() throws SAXException {
count.incrementAndGet();
logger.debug("endDocument");
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void startElement(String ns, String localname, String qname, Attributes atrbts) throws SAXException {
}
@Override
public void endElement(String ns, String localname, String qname) throws SAXException {
}
@Override
public void characters(char[] chars, int pos, int len) throws SAXException {
}
};
File file = File.createTempFile("dnb-bib-pica.", ".xml");
file.deleteOnExit();
FileWriter sw = new FileWriter(file);
while (listRecordsRequest != null) {
try {
ListRecordsResponse listRecordsResponse = new ListRecordsResponse(listRecordsRequest);
listRecordsRequest.addHandler(simpleMetadataHandler);
HttpClient httpClient = client.getHttpClient();
AggregatedHttpMessage response = httpClient.execute(HttpHeaders.of(HttpMethod.GET, listRecordsRequest.getPath())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
String content = response.content().toStringUtf8();
listRecordsResponse.receivedResponse(response, sw);
listRecordsRequest = client.resume(listRecordsRequest, listRecordsResponse.getResumptionToken());
} catch (IOException e) {
logger.error(e.getMessage(), e);
listRecordsRequest = null;
}
}
sw.close();
client.close();
logger.info("count={}", count.get());
} catch (ConnectException | ExecutionException e) {
logger.warn("skipped, can not connect");
} catch (IOException e) {
logger.warn("skipped, HTTP exception");
}
}
}

View file

@ -0,0 +1,142 @@
package org.xbib.oai.client;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.xbib.helianthus.client.Clients;
import org.xbib.helianthus.client.http.HttpClient;
import org.xbib.helianthus.common.http.AggregatedHttpMessage;
import org.xbib.helianthus.common.http.HttpHeaderNames;
import org.xbib.helianthus.common.http.HttpHeaders;
import org.xbib.helianthus.common.http.HttpMethod;
import org.xbib.oai.client.identify.IdentifyRequest;
import org.xbib.oai.client.identify.IdentifyResponse;
import org.xbib.oai.client.listrecords.ListRecordsRequest;
import org.xbib.oai.client.listrecords.ListRecordsResponse;
import org.xbib.oai.xml.SimpleMetadataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.URI;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertTrue;
/**
*
*/
public class DOAJClientTest {
private static final Logger logger = LogManager.getLogger(DOAJClientTest.class.getName());
@Test
public void testListRecordsDOAJ() throws InterruptedException, TimeoutException, IOException {
try {
// will redirect to https://doaj.org/oai
OAIClient oaiClient = OAIClientFactory.newClient("http://doaj.org/oai", true);
IdentifyRequest identifyRequest = oaiClient.newIdentifyRequest();
HttpClient client = oaiClient.getHttpClient();
AggregatedHttpMessage response = client.execute(HttpHeaders.of(HttpMethod.GET, identifyRequest.getPath())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
// follow a maximum of 10 HTTP redirects
int max = 10;
while (response.followUrl() != null && max-- > 0) {
URI uri = URI.create(response.followUrl());
client = Clients.newClient(oaiClient.getFactory(), "none+" + uri, HttpClient.class);
response = client.execute(HttpHeaders.of(HttpMethod.GET, response.followUrl())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
}
IdentifyResponse identifyResponse = new IdentifyResponse();
String content = response.content().toStringUtf8();
logger.debug("identifyResponse = {}", content);
identifyResponse.receivedResponse(response, new StringWriter());
String granularity = identifyResponse.getGranularity();
logger.info("granularity = {}", granularity);
DateTimeFormatter dateTimeFormatter = "YYYY-MM-DD".equals(granularity) ?
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("GMT")) : null;
ListRecordsRequest listRecordsRequest = oaiClient.newListRecordsRequest();
listRecordsRequest.setDateTimeFormatter(dateTimeFormatter);
listRecordsRequest.setFrom(Instant.parse("2016-01-06T00:00:00Z"));
listRecordsRequest.setUntil(Instant.parse("2016-11-07T00:00:00Z"));
listRecordsRequest.setMetadataPrefix("oai_dc");
final AtomicLong count = new AtomicLong(0L);
SimpleMetadataHandler simpleMetadataHandler = new SimpleMetadataHandler() {
@Override
public void startDocument() throws SAXException {
logger.debug("start doc");
}
@Override
public void endDocument() throws SAXException {
logger.debug("end doc");
count.incrementAndGet();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void startElement(String ns, String localname, String qname, Attributes atrbts) throws SAXException {
}
@Override
public void endElement(String ns, String localname, String qname) throws SAXException {
}
@Override
public void characters(char[] chars, int pos, int len) throws SAXException {
}
};
File file = File.createTempFile("doaj.", ".xml");
file.deleteOnExit();
FileWriter fileWriter = new FileWriter(file);
do {
try {
listRecordsRequest.addHandler(simpleMetadataHandler);
client = oaiClient.getHttpClient();
response = client.execute(HttpHeaders.of(HttpMethod.GET, listRecordsRequest.getPath())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
// follow a maximum of 10 HTTP redirects
max = 10;
while (response.followUrl() != null && max-- > 0) {
URI uri = URI.create(response.followUrl());
client = Clients.newClient(oaiClient.getFactory(), "none+" + uri, HttpClient.class);
response = client.execute(HttpHeaders.of(HttpMethod.GET, response.followUrl())
.set(HttpHeaderNames.ACCEPT, "utf-8")).aggregate().get();
}
ListRecordsResponse listRecordsResponse = new ListRecordsResponse(listRecordsRequest);
logger.debug("response = {}", response.headers());
listRecordsResponse.receivedResponse(response, fileWriter);
listRecordsRequest = oaiClient.resume(listRecordsRequest, listRecordsResponse.getResumptionToken());
} catch (IOException e) {
logger.error(e.getMessage(), e);
listRecordsRequest = null;
}
} while (listRecordsRequest != null);
fileWriter.close();
oaiClient.close();
logger.info("count={}", count.get());
assertTrue(count.get() > 0L);
} catch (ConnectException | ExecutionException e) {
logger.warn("skipped, can not connect, exception is:", e);
} catch (InterruptedException | IOException e) {
throw e;
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for testing OAI client.
*/
package org.xbib.oai.client;

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{ABSOLUTE}][%-5p][%-25c][%t] %m%n"/>
</Console>
</appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</configuration>

View file

@ -0,0 +1 @@
uri=http://services.dnb.de/oai/repository

View file

@ -0,0 +1 @@
uri=http://doaj.org/oai

View file

@ -0,0 +1 @@
uri=http://services.dnb.de/oai/repository

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?><OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2013-06-19T07:11:37Z</responseDate>
<request verb="ListRecords" from="2013-01-01T00:00:00Z" until="2013-01-02T00:00:00Z" metadataPrefix="oai_dc">http://doaj.org/oai</request>
<ListRecords>
<record>
<header>
<identifier>oai:doaj.org:2011-9860</identifier>
<datestamp>2013-01-01T12:30:30Z</datestamp>
<setSpec>Biology_and_Life_Sciences</setSpec>
</header>
<metadata>
<oai_dc:dc xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd">
<dc:title>Morfolia</dc:title>
<dc:identifier>http://www.revistas.unal.edu.co/index.php/morfolia</dc:identifier>
<dc:identifier>http://www.doaj.org/doaj?func=openurl&amp;genre=journal&amp;issn=20119860</dc:identifier>
<dc:identifier>issn: 2011-9860</dc:identifier>
<dc:publisher>Universidad Nacional de Colombia</dc:publisher>
<dc:date>2008</dc:date>
<dc:language>Spanish</dc:language>
<dc:subject>morphology</dc:subject>
<dc:subject>anatomy</dc:subject>
<dc:subject>histology</dc:subject>
<dc:subject>embryology</dc:subject>
<dc:subject>genetics</dc:subject>
<dc:subject>DoajSubjectTerm: Biology
</dc:subject>
<dc:subject>LCC: QH301-705.5</dc:subject>
</oai_dc:dc>
</metadata>
</record>
<record>
<header>
<identifier>oai:doaj.org:1091-1774</identifier>
<datestamp>2013-01-01T01:02:00Z</datestamp>
<setSpec>Social_Sciences</setSpec>
</header>
<metadata>
<oai_dc:dc xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd">
<dc:title>Hmong Studies Journal</dc:title>
<dc:identifier>http://www.hmongstudies.org/HmongStudiesJournal.html </dc:identifier>
<dc:identifier>http://www.doaj.org/doaj?func=openurl&amp;genre=journal&amp;issn=10911774</dc:identifier>
<dc:identifier>issn: 1091-1774</dc:identifier>
<dc:publisher>Hmong Studies Journal</dc:publisher>
<dc:date>1996</dc:date>
<dc:language>English</dc:language>
<dc:subject>Hmong culture</dc:subject>
<dc:subject>Hmong history</dc:subject>
<dc:subject>southeast Asian Americans</dc:subject>
<dc:subject>Asian American studies</dc:subject>
<dc:subject>southeast Asian studies</dc:subject>
<dc:subject>DoajSubjectTerm: Social Sciences
</dc:subject>
<dc:subject>LCC: H1-99</dc:subject>
<dc:subject>LCC: HD28-9999</dc:subject>
</oai_dc:dc>
</metadata>
</record>
<resumptionToken cursor="0" completeListSize="2"/>
</ListRecords>
</OAI-PMH>

View file

@ -0,0 +1,41 @@
# XML namespace
xml = http://www.w3.org/XML/1998/namespace
xsl = http://www.w3.org/1999/XSL/Transform
# Atom
atom = http://www.w3.org/2005/Atom
# RDF namespace
rdf = http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs = http://www.w3.org/2000/01/rdf-schema#
owl = http://www.w3.org/2002/07/owl#
foaf = http://xmlns.com/foaf/0.1/
# Apache
xalan = http://xml.apache.org/xslt
# Dublin Core Namespaces
# http://dublincore.org/documents/dcmi-namespace/
dc = http://purl.org/dc/elements/1.1/
dcterms = http://purl.org/dc/terms/
dcam = http://purl.org/dc/dcam/
dcmitype http://purl.org/dc/dcmitype/
rel = http://purl.org/vocab/relationship/
# Library of Congress
marcrel = http://www.loc.gov/loc.terms/relators/
mods = http://www.loc.gov/mods/v3
bib = info:srw/cql-context-set/1/bib-v1/
# RDA, MARC
rdagr2 = http://RDVocab.info/ElementsGr2/
marclang = http://marccodes.heroku.com/languages/
# DNB
gnd = http://d-nb.info/standards/elementset/gnd#
gndvocab = http://d-nb.info/standards/vocab/gnd/
# xbib
xbib = http://xbib.org/elements/

View file

@ -0,0 +1,659 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
XSL Transform to convert OAI 2.0 responses into XHTML
By Christopher Gutteridge, University of Southampton
v1.1
-->
<!--
Copyright (c) 2006 University of Southampton, UK. SO17 1BJ.
EPrints 3 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
EPrints 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EPrints 3; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<!--
All the elements really needed for EPrints are done but if
you want to use this XSL for other OAI archive you may want
to make some minor changes or additions.
Not Done
The 'about' section of 'record'
The 'compession' part of 'identify'
The optional attributes of 'resumptionToken'
The optional 'setDescription' container of 'set'
All the links just link to oai_dc versions of records.
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oai="http://www.openarchives.org/OAI/2.0/"
>
<xsl:output method="html"/>
<xsl:template name="style">
td.value {
vertical-align: top;
padding-left: 1em;
padding: 3px;
}
td.key {
background-color: #e0e0ff;
padding: 3px;
text-align: right;
border: 1px solid #c0c0c0;
white-space: nowrap;
font-weight: bold;
vertical-align: top;
}
.dcdata td.key {
background-color: #ffffe0;
}
body {
margin: 1em 2em 1em 2em;
}
h1, h2, h3 {
font-family: sans-serif;
clear: left;
}
h1 {
padding-bottom: 4px;
margin-bottom: 0px;
}
h2 {
margin-bottom: 0.5em;
}
h3 {
margin-bottom: 0.3em;
font-size: medium;
}
.link {
border: 1px outset #88f;
background-color: #c0c0ff;
padding: 1px 4px 1px 4px;
font-size: 80%;
text-decoration: none;
font-weight: bold;
font-family: sans-serif;
color: black;
}
.link:hover {
color: red;
}
.link:active {
color: red;
border: 1px inset #88f;
background-color: #a0a0df;
}
.oaiRecord, .oaiRecordTitle {
background-color: #f0f0ff;
border-style: solid;
border-color: #d0d0d0;
}
h2.oaiRecordTitle {
background-color: #e0e0ff;
font-size: medium;
font-weight: bold;
padding: 10px;
border-width: 2px 2px 0px 2px;
margin: 0px;
}
.oaiRecord {
margin-bottom: 3em;
border-width: 2px;
padding: 10px;
}
.results {
margin-bottom: 1.5em;
}
ul.quicklinks {
margin-top: 2px;
padding: 4px;
text-align: left;
border-bottom: 2px solid #ccc;
border-top: 2px solid #ccc;
clear: left;
}
ul.quicklinks li {
font-size: 80%;
display: inline;
list-stlye: none;
font-family: sans-serif;
}
p.intro {
font-size: 80%;
}
<xsl:call-template name='xmlstyle' />
</xsl:template>
<xsl:variable name='identifier' select="substring-before(concat(substring-after(/oai:OAI-PMH/oai:request,'identifier='),'&amp;'),'&amp;')" />
<xsl:template match="/">
<html>
<head>
<title>OAI 2.0 Request Results</title>
<style><xsl:call-template name="style"/></style>
</head>
<body>
<h1>OAI 2.0 Request Results</h1>
<xsl:call-template name="quicklinks"/>
<p class="intro">You are viewing an HTML version of the XML OAI response. To see the underlying XML use your web browsers view source option. More information about this XSLT is at the <a href="#moreinfo">bottom of the page</a>.</p>
<xsl:apply-templates select="/oai:OAI-PMH" />
<xsl:call-template name="quicklinks"/>
<h2><a name="moreinfo">About the XSLT</a></h2>
<p>An XSLT file has converted the <a href="http://www.openarchives.org">OAI-PMH 2.0</a> responses into XHTML which looks nice in a browser which supports XSLT such as Mozilla, Firebird and Internet Explorer. The XSLT file was created by <a href="http://www.ecs.soton.ac.uk/people/cjg">Christopher Gutteridge</a> at the University of Southampton as part of the <a href="http://www.eprints.org/software/">GNU EPrints system</a>, and is freely redistributable under the <a href="http://www.gnu.org">GPL</a>.</p><p>If you want to use the XSL file on your own OAI interface you may but due to the way XSLT works you must install the XSL file on the same server as the OAI script, you can't just link to this copy.</p><p>For more information or to download the XSL file please see the <a href="http://software.eprints.org/xslt.php">OAI to XHTML XSLT homepage</a>.</p>
</body>
</html>
</xsl:template>
<xsl:template name="quicklinks">
<ul class="quicklinks">
<li><a href="?verb=Identify">Identify</a> | </li>
<li><a href="?verb=ListRecords&amp;metadataPrefix=oai_dc">ListRecords</a> | </li>
<li><a href="?verb=ListSets">ListSets</a> | </li>
<li><a href="?verb=ListMetadataFormats">ListMetadataFormats</a> | </li>
<li><a href="?verb=ListIdentifiers&amp;metadataPrefix=oai_dc">ListIdentifiers</a></li>
</ul>
</xsl:template>
<xsl:template match="/oai:OAI-PMH">
<table class="values">
<tr><td class="key">Datestamp of response</td>
<td class="value"><xsl:value-of select="oai:responseDate"/></td></tr>
<tr><td class="key">Request URL</td>
<td class="value"><xsl:value-of select="oai:request"/></td></tr>
</table>
<!-- verb: [<xsl:value-of select="oai:request/@verb" />]<br /> -->
<xsl:choose>
<xsl:when test="oai:error">
<h2>OAI Error(s)</h2>
<p>The request could not be completed due to the following error or errors.</p>
<div class="results">
<xsl:apply-templates select="oai:error"/>
</div>
</xsl:when>
<xsl:otherwise>
<p>Request was of type <xsl:value-of select="oai:request/@verb"/>.</p>
<div class="results">
<xsl:apply-templates select="oai:Identify" />
<xsl:apply-templates select="oai:GetRecord"/>
<xsl:apply-templates select="oai:ListRecords"/>
<xsl:apply-templates select="oai:ListSets"/>
<xsl:apply-templates select="oai:ListMetadataFormats"/>
<xsl:apply-templates select="oai:ListIdentifiers"/>
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- ERROR -->
<xsl:template match="/oai:OAI-PMH/oai:error">
<table class="values">
<tr><td class="key">Error Code</td>
<td class="value"><xsl:value-of select="@code"/></td></tr>
</table>
<p class="error"><xsl:value-of select="." /></p>
</xsl:template>
<!-- IDENTIFY -->
<xsl:template match="/oai:OAI-PMH/oai:Identify">
<table class="values">
<tr><td class="key">Repository Name</td>
<td class="value"><xsl:value-of select="oai:repositoryName"/></td></tr>
<tr><td class="key">Base URL</td>
<td class="value"><xsl:value-of select="oai:baseURL"/></td></tr>
<tr><td class="key">Protocol Version</td>
<td class="value"><xsl:value-of select="oai:protocolVersion"/></td></tr>
<tr><td class="key">Earliest Datestamp</td>
<td class="value"><xsl:value-of select="oai:earliestDatestamp"/></td></tr>
<tr><td class="key">Deleted Record Policy</td>
<td class="value"><xsl:value-of select="oai:deletedRecord"/></td></tr>
<tr><td class="key">Granularity</td>
<td class="value"><xsl:value-of select="oai:granularity"/></td></tr>
<xsl:apply-templates select="oai:adminEmail"/>
</table>
<xsl:apply-templates select="oai:description"/>
<!--no warning about unsupported descriptions -->
</xsl:template>
<xsl:template match="/oai:OAI-PMH/oai:Identify/oai:adminEmail">
<tr><td class="key">Admin Email</td>
<td class="value"><xsl:value-of select="."/></td></tr>
</xsl:template>
<!--
Identify / Unsupported Description
-->
<xsl:template match="oai:description/*" priority="-100">
<h2>Unsupported Description Type</h2>
<p>The XSL currently does not support this type of description.</p>
<div class="xmlSource">
<xsl:apply-templates select="." mode='xmlMarkup' />
</div>
</xsl:template>
<!--
Identify / OAI-Identifier
-->
<xsl:template match="id:oai-identifier" xmlns:id="http://www.openarchives.org/OAI/2.0/oai-identifier">
<h2>OAI-Identifier</h2>
<table class="values">
<tr><td class="key">Scheme</td>
<td class="value"><xsl:value-of select="id:scheme"/></td></tr>
<tr><td class="key">Repository Identifier</td>
<td class="value"><xsl:value-of select="id:repositoryIdentifier"/></td></tr>
<tr><td class="key">Delimiter</td>
<td class="value"><xsl:value-of select="id:delimiter"/></td></tr>
<tr><td class="key">Sample OAI Identifier</td>
<td class="value"><xsl:value-of select="id:sampleIdentifier"/></td></tr>
</table>
</xsl:template>
<!--
Identify / EPrints
-->
<xsl:template match="ep:eprints" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
<h2>EPrints Description</h2>
<xsl:if test="ep:content">
<h3>Content</h3>
<xsl:apply-templates select="ep:content"/>
</xsl:if>
<xsl:if test="ep:submissionPolicy">
<h3>Submission Policy</h3>
<xsl:apply-templates select="ep:submissionPolicy"/>
</xsl:if>
<h3>Metadata Policy</h3>
<xsl:apply-templates select="ep:metadataPolicy"/>
<h3>Data Policy</h3>
<xsl:apply-templates select="ep:dataPolicy"/>
<xsl:apply-templates select="ep:comment"/>
</xsl:template>
<xsl:template match="ep:content|ep:dataPolicy|ep:metadataPolicy|ep:submissionPolicy" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
<xsl:if test="ep:text">
<p><xsl:value-of select="ep:text" /></p>
</xsl:if>
<xsl:if test="ep:URL">
<div><a href="{ep:URL}"><xsl:value-of select="ep:URL" /></a></div>
</xsl:if>
</xsl:template>
<xsl:template match="ep:comment" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
<h3>Comment</h3>
<div><xsl:value-of select="."/></div>
</xsl:template>
<!--
Identify / Friends
-->
<xsl:template match="fr:friends" xmlns:fr="http://www.openarchives.org/OAI/2.0/friends/">
<h2>Friends</h2>
<ul>
<xsl:apply-templates select="fr:baseURL"/>
</ul>
</xsl:template>
<xsl:template match="fr:baseURL" xmlns:fr="http://www.openarchives.org/OAI/2.0/friends/">
<li><xsl:value-of select="."/>
<xsl:text> </xsl:text>
<a class="link" href="{.}?verb=Identify">Identify</a></li>
</xsl:template>
<!--
Identify / Branding
-->
<xsl:template match="br:branding" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
<h2>Branding</h2>
<xsl:apply-templates select="br:collectionIcon"/>
<xsl:apply-templates select="br:metadataRendering"/>
</xsl:template>
<xsl:template match="br:collectionIcon" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
<h3>Icon</h3>
<xsl:choose>
<xsl:when test="link!=''">
<a href="{br:link}"><img src="{br:url}" alt="{br:title}" width="{br:width}" height="{br:height}" border="0" /></a>
</xsl:when>
<xsl:otherwise>
<img src="{br:url}" alt="{br:title}" width="{br:width}" height="{br:height}" border="0" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="br:metadataRendering" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
<h3>Metadata Rendering Rule</h3>
<table class="values">
<tr><td class="key">URL</td>
<td class="value"><xsl:value-of select="."/></td></tr>
<tr><td class="key">Namespace</td>
<td class="value"><xsl:value-of select="@metadataNamespace"/></td></tr>
<tr><td class="key">Mime Type</td>
<td class="value"><xsl:value-of select="@mimetype"/></td></tr>
</table>
</xsl:template>
<!--
Identify / Gateway
-->
<xsl:template match="gw:gateway" xmlns:gw="http://www.openarchives.org/OAI/2.0/gateway/x">
<h2>Gateway Information</h2>
<table class="values">
<tr><td class="key">Source</td>
<td class="value"><xsl:value-of select="gw:source"/></td></tr>
<tr><td class="key">Description</td>
<td class="value"><xsl:value-of select="gw:gatewayDescription"/></td></tr>
<xsl:apply-templates select="gw:gatewayAdmin"/>
<xsl:if test="gw:gatewayURL">
<tr><td class="key">URL</td>
<td class="value"><xsl:value-of select="gw:gatewayURL"/></td></tr>
</xsl:if>
<xsl:if test="gw:gatewayNotes">
<tr><td class="key">Notes</td>
<td class="value"><xsl:value-of select="gw:gatewayNotes"/></td></tr>
</xsl:if>
</table>
</xsl:template>
<xsl:template match="gw:gatewayAdmin" xmlns:gw="http://www.openarchives.org/OAI/2.0/gateway/">
<tr><td class="key">Admin</td>
<td class="value"><xsl:value-of select="."/></td></tr>
</xsl:template>
<!-- GetRecord -->
<xsl:template match="oai:GetRecord">
<xsl:apply-templates select="oai:record" />
</xsl:template>
<!-- ListRecords -->
<xsl:template match="oai:ListRecords">
<xsl:apply-templates select="oai:record" />
<xsl:apply-templates select="oai:resumptionToken" />
</xsl:template>
<!-- ListIdentifiers -->
<xsl:template match="oai:ListIdentifiers">
<xsl:apply-templates select="oai:header" />
<xsl:apply-templates select="oai:resumptionToken" />
</xsl:template>
<!-- ListSets -->
<xsl:template match="oai:ListSets">
<xsl:apply-templates select="oai:set" />
<xsl:apply-templates select="oai:resumptionToken" />
</xsl:template>
<xsl:template match="oai:set">
<h2>Set</h2>
<table class="values">
<tr><td class="key">setName</td>
<td class="value"><xsl:value-of select="oai:setName"/></td></tr>
<xsl:apply-templates select="oai:setSpec" />
</table>
</xsl:template>
<!-- ListMetadataFormats -->
<xsl:template match="oai:ListMetadataFormats">
<xsl:choose>
<xsl:when test="$identifier">
<p>This is a list of metadata formats available for the record "<xsl:value-of select='$identifier' />". Use these links to view the metadata: <xsl:apply-templates select="oai:metadataFormat/oai:metadataPrefix" /></p>
</xsl:when>
<xsl:otherwise>
<p>This is a list of metadata formats available from this archive.</p>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates select="oai:metadataFormat" />
</xsl:template>
<xsl:template match="oai:metadataFormat">
<h2>Metadata Format</h2>
<table class="values">
<tr><td class="key">metadataPrefix</td>
<td class="value"><a class="link" href="?verb=ListRecords&amp;metadataPrefix={oai:metadataPrefix}"><xsl:value-of select="oai:metadataPrefix"/></a></td></tr>
<tr><td class="key">metadataNamespace</td>
<td class="value"><xsl:value-of select="oai:metadataNamespace"/></td></tr>
<tr><td class="key">schema</td>
<td class="value"><a href="{oai:schema}"><xsl:value-of select="oai:schema"/></a></td></tr>
</table>
</xsl:template>
<xsl:template match="oai:metadataPrefix">
<xsl:text> </xsl:text><a class="link" href="?verb=GetRecord&amp;metadataPrefix={.}&amp;identifier={$identifier}"><xsl:value-of select='.' /></a>
</xsl:template>
<!-- record object -->
<xsl:template match="oai:record">
<h2 class="oaiRecordTitle">OAI Record: <xsl:value-of select="oai:header/oai:identifier"/></h2>
<div class="oaiRecord">
<xsl:apply-templates select="oai:header" />
<xsl:apply-templates select="oai:metadata" />
<xsl:apply-templates select="oai:about" />
</div>
</xsl:template>
<xsl:template match="oai:header">
<h3>OAI Record Header</h3>
<table class="values">
<tr><td class="key">OAI Identifier</td>
<td class="value">
<xsl:value-of select="oai:identifier"/>
<xsl:text> </xsl:text><a class="link" href="?verb=GetRecord&amp;metadataPrefix=oai_dc&amp;identifier={oai:identifier}">oai_dc</a>
<xsl:text> </xsl:text><a class="link" href="?verb=ListMetadataFormats&amp;identifier={oai:identifier}">formats</a>
</td></tr>
<tr><td class="key">Datestamp</td>
<td class="value"><xsl:value-of select="oai:datestamp"/></td></tr>
<xsl:apply-templates select="oai:setSpec" />
</table>
<xsl:if test="@status='deleted'">
<p>This record has been deleted.</p>
</xsl:if>
</xsl:template>
<xsl:template match="oai:about">
<p>"about" part of record container not supported by the XSL</p>
</xsl:template>
<xsl:template match="oai:metadata">
&#160;
<div class="metadata">
<xsl:apply-templates select="*" />
</div>
</xsl:template>
<!-- oai setSpec object -->
<xsl:template match="oai:setSpec">
<tr><td class="key">setSpec</td>
<td class="value"><xsl:value-of select="."/>
<xsl:text> </xsl:text><a class="link" href="?verb=ListIdentifiers&amp;metadataPrefix=oai_dc&amp;set={.}">Identifiers</a>
<xsl:text> </xsl:text><a class="link" href="?verb=ListRecords&amp;metadataPrefix=oai_dc&amp;set={.}">Records</a>
</td></tr>
</xsl:template>
<!-- oai resumptionToken -->
<xsl:template match="oai:resumptionToken">
<p>There are more results.</p>
<table class="values">
<tr><td class="key">resumptionToken:</td>
<td class="value"><xsl:value-of select="."/>
<xsl:text> </xsl:text>
<a class="link" href="?verb={/oai:OAI-PMH/oai:request/@verb}&amp;resumptionToken={.}">Resume</a></td></tr>
</table>
</xsl:template>
<!-- unknown metadata format -->
<xsl:template match="oai:metadata/*" priority='-100'>
<h3>Unknown Metadata Format</h3>
<div class="xmlSource">
<xsl:apply-templates select="." mode='xmlMarkup' />
</div>
</xsl:template>
<!-- oai_dc record -->
<xsl:template match="oai_dc:dc" xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" >
<div class="dcdata">
<h3>Dublin Core Metadata (oai_dc)</h3>
<table class="dcdata">
<xsl:apply-templates select="*" />
</table>
</div>
</xsl:template>
<xsl:template match="dc:title" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Title</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:creator" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Author or Creator</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:subject" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Subject and Keywords</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:description" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Description</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:publisher" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Publisher</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:contributor" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Other Contributor</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:date" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Date</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:type" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Resource Type</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:format" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Format</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:identifier" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Resource Identifier</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:source" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Source</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:language" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Language</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:relation" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Relation</td><td class="value">
<xsl:choose>
<xsl:when test='starts-with(.,"http" )'>
<xsl:choose>
<xsl:when test='string-length(.) &gt; 50'>
<a class="link" href="{.}">URL</a>
<i> URL not shown as it is very long.</i>
</xsl:when>
<xsl:otherwise>
<a href="{.}"><xsl:value-of select="."/></a>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</td></tr></xsl:template>
<xsl:template match="dc:coverage" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Coverage</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<xsl:template match="dc:rights" xmlns:dc="http://purl.org/dc/elements/1.1/">
<tr><td class="key">Rights Management</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
<!-- XML Pretty Maker -->
<xsl:template match="node()" mode='xmlMarkup'>
<div class="xmlBlock">
&lt;<span class="xmlTagName"><xsl:value-of select='name(.)' /></span><xsl:apply-templates select="@*" mode='xmlMarkup'/>&gt;<xsl:apply-templates select="node()" mode='xmlMarkup' />&lt;/<span class="xmlTagName"><xsl:value-of select='name(.)' /></span>&gt;
</div>
</xsl:template>
<xsl:template match="text()" mode='xmlMarkup'><span class="xmlText"><xsl:value-of select='.' /></span></xsl:template>
<xsl:template match="@*" mode='xmlMarkup'>
<xsl:text> </xsl:text><span class="xmlAttrName"><xsl:value-of select='name()' /></span>="<span class="xmlAttrValue"><xsl:value-of select='.' /></span>"
</xsl:template>
<xsl:template name="xmlstyle">
.xmlSource {
font-size: 70%;
border: solid #c0c0a0 1px;
background-color: #ffffe0;
padding: 2em 2em 2em 0em;
}
.xmlBlock {
padding-left: 2em;
}
.xmlTagName {
color: #800000;
font-weight: bold;
}
.xmlAttrName {
font-weight: bold;
}
.xmlAttrValue {
color: #0000c0;
}
</xsl:template>
</xsl:stylesheet>

6
oai-common/build.gradle Normal file
View file

@ -0,0 +1,6 @@
dependencies {
compile "org.xbib:content-rdf:1.0.3"
//testCompile "xerces:xercesImpl:${versions.xerces}"
//testCompile "xalan:xalan:${versions.xalan}"
//testCompile "com.fasterxml.woodstox:woodstox-core:${versions.woodstox}"
}

View file

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<property name="groups" value="com,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>

View file

@ -0,0 +1,13 @@
package org.xbib.oai;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @param <R> response type parameter
*/
public abstract class DefaultOAIResponseListener<R extends OAIResponse> {
}

View file

@ -0,0 +1,49 @@
package org.xbib.oai;
/**
*
*/
public interface OAIConstants {
String USER_AGENT = "OAI/20161111";
String NS_URI = "http://www.openarchives.org/OAI/2.0/";
String NS_PREFIX = "oai";
String OAIDC_NS_URI = "http://www.openarchives.org/OAI/2.0/oai_dc/";
String OAIDC_NS_PREFIX = "oai_dc";
String DC_NS_URI = "http://www.purl.org/dc/elements/1.1/";
String DC_PREFIX = "dc";
String VERB_PARAMETER = "verb";
String IDENTIFY = "Identify";
String LIST_METADATA_FORMATS = "ListMetadataFormats";
String LIST_SETS = "ListSets";
String LIST_RECORDS = "ListRecords";
String LIST_IDENTIFIERS = "ListIdentifiers";
String GET_RECORD = "GetRecord";
String FROM_PARAMETER = "from";
String UNTIL_PARAMETER = "until";
String SET_PARAMETER = "set";
String METADATA_PREFIX_PARAMETER = "metadataPrefix";
String RESUMPTION_TOKEN_PARAMETER = "resumptionToken";
String IDENTIFIER_PARAMETER = "identifier";
String REQUEST = "request";
}

View file

@ -0,0 +1,23 @@
package org.xbib.oai;
import org.xbib.oai.util.ResumptionToken;
import java.time.Instant;
/**
* OAI request API.
*/
public interface OAIRequest extends OAIConstants {
void setSet(String set);
void setMetadataPrefix(String prefix);
void setFrom(Instant from);
void setUntil(Instant until);
void setResumptionToken(ResumptionToken<?> token);
ResumptionToken<?> getResumptionToken();
}

View file

@ -0,0 +1,12 @@
package org.xbib.oai;
import java.io.IOException;
import java.io.Writer;
/**
* OAI response.
*/
public interface OAIResponse {
void to(Writer writer) throws IOException;
}

View file

@ -0,0 +1,10 @@
package org.xbib.oai;
import java.io.Closeable;
/**
* OAI session.
*/
public interface OAISession extends Closeable {
}

View file

@ -0,0 +1,17 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class BadArgumentException extends OAIException {
private static final long serialVersionUID = -6647892792394074500L;
public BadArgumentException() {
this(null);
}
public BadArgumentException(String message) {
super(message);
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.oai.exceptions;
import org.xbib.oai.util.ResumptionToken;
/**
*
*/
public class BadResumptionTokenException extends OAIException {
private static final long serialVersionUID = 7384401627260164303L;
public BadResumptionTokenException(ResumptionToken<?> token) {
super(token != null ? token.toString() : null);
}
}

View file

@ -0,0 +1,13 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class BadVerbException extends OAIException {
private static final long serialVersionUID = 1642129565793325510L;
public BadVerbException(String message) {
super(message);
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class CannotDisseminateFormatException extends OAIException {
private static final long serialVersionUID = 154900133710811545L;
public CannotDisseminateFormatException(String message) {
super(message);
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class IdDoesNotExistException extends OAIException {
private static final long serialVersionUID = 9201985582562843506L;
public IdDoesNotExistException(String message) {
super(message);
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class NoRecordsMatchException extends OAIException {
private static final long serialVersionUID = 5201331168058463772L;
public NoRecordsMatchException(String message) {
super(message);
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.oai.exceptions;
/**
*
*/
public class NoSetHierarchyException extends OAIException {
private static final long serialVersionUID = 6275260694745177314L;
public NoSetHierarchyException(String message) {
super(message);
}
}

View file

@ -0,0 +1,24 @@
package org.xbib.oai.exceptions;
import java.io.IOException;
/**
*
*/
public class OAIException extends IOException {
private static final long serialVersionUID = -1890146067179892744L;
public OAIException(String message) {
super(message);
}
public OAIException(Throwable throwable) {
super(throwable);
}
public OAIException(String message, Throwable throwable) {
super(message, throwable);
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI exceptions.
*/
package org.xbib.oai.exceptions;

View file

@ -0,0 +1,4 @@
/**
* Classes for OAI.
*/
package org.xbib.oai;

View file

@ -0,0 +1,59 @@
package org.xbib.oai.rdf;
import org.xbib.content.rdf.RdfContentParams;
import org.xbib.content.rdf.io.xml.AbstractXmlResourceHandler;
import org.xbib.content.rdf.io.xml.XmlHandler;
import org.xbib.content.resource.IRI;
import org.xbib.content.resource.IRINamespaceContext;
import org.xbib.oai.OAIConstants;
import javax.xml.namespace.QName;
/**
* A default RDF resource handler for OAI.
*/
public class RdfResourceHandler extends AbstractXmlResourceHandler<RdfContentParams> implements OAIConstants {
public RdfResourceHandler(RdfContentParams params) {
super(params);
}
@Override
public void identify(QName name, String value, IRI identifier) {
// do nothing
}
@Override
public boolean isResourceDelimiter(QName name) {
boolean b = OAIDC_NS_URI.equals(name.getNamespaceURI())
&& DC_PREFIX.equals(name.getLocalPart());
return b;
}
@Override
public boolean skip(QName name) {
boolean b = OAIDC_NS_URI.equals(name.getNamespaceURI())
&& DC_PREFIX.equals(name.getLocalPart());
b = b || name.getLocalPart().startsWith("@");
return b;
}
@Override
public void addToPredicate(QName parent, String content) {
// do nothing
}
public Object toObject(QName parent, String content) {
return content;
}
@Override
public XmlHandler<RdfContentParams> setNamespaceContext(IRINamespaceContext namespaceContext) {
return this;
}
@Override
public IRINamespaceContext getNamespaceContext() {
return getParams().getNamespaceContext();
}
}

View file

@ -0,0 +1,140 @@
package org.xbib.oai.rdf;
import org.xbib.content.rdf.RdfContentBuilder;
import org.xbib.content.rdf.RdfContentParams;
import org.xbib.content.rdf.Resource;
import org.xbib.content.rdf.internal.DefaultAnonymousResource;
import org.xbib.content.rdf.io.xml.XmlResourceHandler;
import org.xbib.content.resource.IRI;
import org.xbib.content.resource.IRINamespaceContext;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.xml.SimpleMetadataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.IOException;
/**
* RDF metadata handler.
*/
public class RdfSimpleMetadataHandler extends SimpleMetadataHandler implements OAIConstants {
private RdfResourceHandler handler;
private Resource resource;
private RdfContentBuilder<?> builder;
private RdfContentParams params;
public static IRINamespaceContext getDefaultContext() {
IRINamespaceContext context = IRINamespaceContext.newInstance();
context.addNamespace(DC_PREFIX, DC_NS_URI);
context.addNamespace(OAIDC_NS_PREFIX, OAIDC_NS_URI);
return context;
}
public RdfSimpleMetadataHandler() {
this(RdfSimpleMetadataHandler::getDefaultContext);
}
public RdfSimpleMetadataHandler(RdfContentParams params) {
this.params = params;
this.resource = new DefaultAnonymousResource();
// set up our default handler
this.handler = new RdfResourceHandler(params);
handler.setDefaultNamespace(NS_PREFIX, NS_URI);
}
public IRINamespaceContext getContext() {
return params.getNamespaceContext();
}
public Resource getResource() {
return resource;
}
public RdfSimpleMetadataHandler setHandler(RdfResourceHandler handler) {
handler.setDefaultNamespace(NS_PREFIX, NS_URI);
this.handler = handler;
return this;
}
public XmlResourceHandler<RdfContentParams> getHandler() {
return handler;
}
public RdfSimpleMetadataHandler setBuilder(RdfContentBuilder<?> builder) {
this.builder = builder;
return this;
}
@Override
public void startDocument() throws SAXException {
if (handler != null) {
handler.startDocument();
}
}
/**
* At the endStream of each OAI metadata, the resource context receives the identifier from
* the metadata header. The resource context is pushed to the RDF output.
* Any IOException is converted to a SAXException.
*
* @throws SAXException if SaX fails
*/
@Override
public void endDocument() throws SAXException {
String id = getHeader().getIdentifier().trim();
if (handler != null) {
handler.identify(null, id, null);
resource.setId(IRI.create(id));
handler.endDocument();
try {
if (builder != null) {
builder.receive(resource);
}
} catch (IOException e) {
throw new SAXException(e);
}
}
}
@Override
public void startPrefixMapping(String prefix, String namespaceURI) throws SAXException {
if (handler != null) {
handler.startPrefixMapping(prefix, namespaceURI);
if (prefix.isEmpty()) {
handler.setDefaultNamespace("oai", namespaceURI);
}
}
}
@Override
public void endPrefixMapping(String string) throws SAXException {
if (handler != null) {
handler.endPrefixMapping(string);
}
}
@Override
public void startElement(String ns, String localname, String string2, Attributes atrbts) throws SAXException {
if (handler != null) {
handler.startElement(ns, localname, string2, atrbts);
}
}
@Override
public void endElement(String ns, String localname, String string2) throws SAXException {
if (handler != null) {
handler.endElement(ns, localname, string2);
}
}
@Override
public void characters(char[] chars, int i, int i1) throws SAXException {
if (handler != null) {
handler.characters(chars, i, i1);
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for RDF in OAI.
*/
package org.xbib.oai.rdf;

View file

@ -0,0 +1,42 @@
package org.xbib.oai.util;
import java.time.Instant;
/**
*
*/
public class RecordHeader {
private String identifier;
private Instant date;
private String set;
public RecordHeader setIdentifier(String identifier) {
this.identifier = identifier;
return this;
}
public String getIdentifier() {
return identifier;
}
public RecordHeader setDate(Instant date) {
this.date = date;
return this;
}
public Instant getDate() {
return date;
}
public RecordHeader setSetspec(String setSpec) {
this.set = setSpec;
return this;
}
public String getSetSpec() {
return set;
}
}

View file

@ -0,0 +1,171 @@
package org.xbib.oai.util;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @param <T> token parameter type
*/
public class ResumptionToken<T> {
private static final int DEFAULT_INTERVAL_SIZE = 1000;
private static final ConcurrentHashMap<UUID, ResumptionToken<?>> cache = new ConcurrentHashMap<>();
private final UUID uuid;
private final int interval;
private int position;
private T value;
private Date expirationDate;
private int completeListSize;
private int cursor;
private String metadataPrefix;
private String set;
private Date from;
private Date until;
private boolean completed;
private ResumptionToken() {
this(DEFAULT_INTERVAL_SIZE);
this.completed = false;
}
private ResumptionToken(int interval) {
this.uuid = UUID.randomUUID();
this.position = 0;
this.interval = interval;
this.value = null;
cache.put(uuid, this);
}
public static <T> ResumptionToken<T> newToken(T value) {
return new ResumptionToken<T>().setValue(value);
}
public static ResumptionToken<?> get(UUID token) {
return cache.get(token);
}
public UUID getKey() {
return uuid;
}
public ResumptionToken<T> setPosition(int position) {
this.position = position;
return this;
}
public int getPosition() {
return position;
}
public int advancePosition() {
setPosition(position + interval);
return getPosition();
}
public int getInterval() {
return interval;
}
public ResumptionToken<T> setValue(T value) {
this.value = value;
return this;
}
public T getValue() {
return value;
}
public ResumptionToken<T> setExpirationDate(Date date) {
this.expirationDate = date;
return this;
}
public Date getExpirationDate() {
return expirationDate;
}
public ResumptionToken<T> setCompleteListSize(int size) {
this.completeListSize = size;
completed = size < interval;
return this;
}
public int getCompleteListSize() {
return completeListSize;
}
public ResumptionToken<T> setCursor(int cursor) {
this.cursor = cursor;
return this;
}
public int getCursor() {
return cursor;
}
public ResumptionToken<T> setMetadataPrefix(String metadataPrefix) {
this.metadataPrefix = metadataPrefix;
return this;
}
public String getMetadataPrefix() {
return metadataPrefix;
}
public ResumptionToken<T> setSet(String set) {
this.set = set;
return this;
}
public String getSet() {
return set;
}
public ResumptionToken<T> setFrom(Date from) {
this.from = from;
return this;
}
public Date getFrom() {
return from;
}
public ResumptionToken<T> setUntil(Date until) {
this.until = until;
return this;
}
public Date getUntil() {
return until;
}
public void update(int completeListSize, int pageSize, int currentPage) {
this.completeListSize = completeListSize;
this.cursor = pageSize * currentPage;
}
public boolean isComplete() {
return completed;
}
@Override
public String toString() {
return value != null ? value.toString() : null;
}
}

View file

@ -0,0 +1,230 @@
package org.xbib.oai.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
/**
*
*/
public class URIBuilder {
private URI uri;
private String scheme;
private String authority;
private String path;
private String fragment;
private Map<String, String> params;
public URIBuilder() {
this.params = new LinkedHashMap<>();
}
public URIBuilder(String base) {
this(URI.create(base));
}
public URIBuilder(URI base) {
this.uri = base;
this.scheme = uri.getScheme();
this.authority = uri.getAuthority();
this.path = uri.getPath();
this.fragment = uri.getFragment();
this.params = parseQueryString(uri);
}
public URIBuilder(URI base, Charset encoding) {
this.uri = base;
this.params = parseQueryString(uri, encoding);
}
public URIBuilder scheme(String scheme) {
this.scheme = scheme;
return this;
}
public URIBuilder authority(String authority) {
this.authority = authority;
return this;
}
public URIBuilder path(String path) {
this.path = path;
return this;
}
public URIBuilder fragment(String fragment) {
this.fragment = fragment;
return this;
}
/**
* This method adds a single key/value parameter to the query
* string of a given URI. Existing keys will be overwritten.
*
* @param key the key
* @param value the value
* @return this URI builder
*/
public URIBuilder addParameter(String key, String value) {
params.put(key, value);
return this;
}
public String buildGetPath() {
return path + (params.isEmpty() ? "" : "?" + URIFormatter.renderQueryString(params));
}
/**
* This method adds a single key/value parameter to the query
* string of a given URI, URI-escaped with the given encoding.
* Existing keys will be overwritten.
*
* @param key the key
* @param value the value
* @param encoding the encoding
* @return this URI builder
*/
public URIBuilder addParameter(String key, String value, Charset encoding) {
params.put(key, URIFormatter.encode(value, encoding));
return this;
}
public URI build() {
try {
return new URI(scheme, authority, path, URIFormatter.renderQueryString(params), fragment);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
public URI build(Charset encoding) {
try {
return new URI(scheme, authority, path, URIFormatter.renderQueryString(params, encoding), fragment);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* This method parses a query string and returns a map of decoded
* request parameters. We do not rely on java.net.URI because it does not
* decode plus characters. The encoding is UTF-8.
*
* @param uri the URI to examine for request parameters
* @return a map
*/
public static Map<String, String> parseQueryString(URI uri) {
return parseQueryString(uri, StandardCharsets.UTF_8);
}
/**
* This method parses a query string and returns a map of decoded
* request parameters. We do not rely on java.net.URI because it does not
* decode plus characters.
*
* @param uri the URI to examine for request parameters
* @param encoding the encoding
* @return a Map
*/
public static Map<String, String> parseQueryString(URI uri, Charset encoding) {
return parseQueryString(uri, encoding, null);
}
/**
* This method parses a query string and returns a map of decoded
* request parameters. We do not rely on java.net.URI because it does not
* decode plus characters. A listener can process the parameters in order.
*
* @param uri the URI to examine for request parameters
* @param encoding the encoding
* @param listener a listner for processing the URI parameters in order, or null
* @return a Map of parameters
*/
public static Map<String, String> parseQueryString(URI uri, Charset encoding, ParameterListener listener) {
if (uri == null) {
throw new IllegalArgumentException();
}
return parseQueryString(uri.getRawQuery(), encoding, listener);
}
public static Map<String, String> parseQueryString(String rawQuery, Charset encoding, ParameterListener listener) {
Map<String, String> m = new HashMap<>();
if (rawQuery == null) {
return m;
}
// we use getRawQuery because we do our decoding by ourselves
StringTokenizer st = new StringTokenizer(rawQuery, "&");
while (st.hasMoreTokens()) {
String pair = st.nextToken();
String k;
String v;
int pos = pair.indexOf('=');
if (pos < 0) {
k = pair;
v = null;
} else {
k = pair.substring(0, pos);
v = decode(pair.substring(pos + 1, pair.length()), encoding);
}
m.put(k, v);
if (listener != null) {
listener.received(k, v);
}
}
return m;
}
/**
* Decodes an octet according to RFC 2396. According to this spec,
* any characters outside the range 0x20 - 0x7E must be escaped because
* they are not printable characters, except for any characters in the
* fragment identifier. This method will translate any escaped characters
* back to the original.
*
* @param octet the octet to decode
* @param encoding the encoding to decode into
* @return The decoded URI
*/
public static String decode(String octet, Charset encoding) {
StringBuilder sb = new StringBuilder();
boolean fragment = false;
for (int i = 0; i < octet.length(); i++) {
char ch = octet.charAt(i);
switch (ch) {
case '+':
sb.append(' ');
break;
case '#':
sb.append(ch);
fragment = true;
break;
case '%':
if (!fragment) {
// fast hex decode
sb.append((char) ((Character.digit(octet.charAt(++i), 16) << 4)
| Character.digit(octet.charAt(++i), 16)));
} else {
sb.append(ch);
}
break;
default:
sb.append(ch);
break;
}
}
return new String(sb.toString().getBytes(StandardCharsets.ISO_8859_1), encoding);
}
/**
*
*/
@FunctionalInterface
public interface ParameterListener {
void received(String k, String v);
}
}

View file

@ -0,0 +1,138 @@
package org.xbib.oai.util;
import java.nio.charset.Charset;
import java.util.Map;
/**
*
*/
public class URIFormatter {
public static String renderQueryString(Map<String, String> m) {
return renderQueryString(m, null, false);
}
public static String renderQueryString(Map<String, String> m, Charset encoding) {
return renderQueryString(m, encoding, true);
}
/**
* This method takes a Map of key/value elements and converts it
* into a URL encoded querystring format.
*
* @param m a map of key/value arrays
* @param encoding the charset
* @param encode true if arameter must be encoded
* @return a string with the URL encoded data
*/
public static String renderQueryString(Map<String, String> m, Charset encoding, boolean encode) {
String key;
String value;
StringBuilder out = new StringBuilder();
for (Map.Entry<String, String> me : m.entrySet()) {
key = me.getKey();
value = encode ? encode(me.getValue(), encoding) : me.getValue();
if (key != null) {
if (out.length() > 0) {
out.append("&");
}
out.append(key);
if ((value != null) && (value.length() > 0)) {
out.append("=").append(value);
}
}
}
return out.toString();
}
/**
* <p>Encode a string into URI syntax</p>
* <p>This function applies the URI escaping rules defined in
* section 2 of [RFC 2396], as amended by [RFC 2732], to the string
* supplied as the first argument, which typically represents all or part
* of a URI, URI reference or IRI. The effect of the function is to
* replace any special character in the string by an escape sequence of
* the form %xx%yy..., where xxyy... is the hexadecimal representation of
* the octets used to represent the character in US-ASCII for characters
* in the ASCII repertoire, and a different character encoding for
* non-ASCII characters.</p>
* <p>If the second argument is true, all characters are escaped
* other than lower case letters a-z, upper case letters A-Z, digits 0-9,
* and the characters referred to in [RFC 2396] as "marks": specifically,
* "-" | "_" | "." | "!" | "~" | "" | "'" | "(" | ")". The "%" character
* itself is escaped only if it is not followed by two hexadecimal digits
* (that is, 0-9, a-f, and A-F).</p>
* <p>[RFC 2396] does not define whether escaped URIs should use
* lower case or upper case for hexadecimal digits. To ensure that escaped
* URIs can be compared using string comparison functions, this function
* must always use the upper-case letters A-F.</p>
* <p>The character encoding used as the basis for determining the
* octets depends on the setting of the second argument.</p>
*
* @param s the String to convert
* @param encoding The encoding to use for unsafe characters
* @return The converted String
*/
public static String encode(String s, Charset encoding) {
if (s == null) {
return null;
}
int length = s.length();
int start = 0;
int i = 0;
StringBuilder result = new StringBuilder(length);
while (true) {
while ((i < length) && isSafe(s.charAt(i))) {
i++;
}
// Safe character can just be added
result.append(s.substring(start, i));
// Are we done?
if (i >= length) {
return result.toString();
} else if (s.charAt(i) == ' ') {
result.append('+'); // Replace space char with plus symbol.
i++;
} else {
// Get all unsafe characters
start = i;
char c;
while ((i < length) && ((c = s.charAt(i)) != ' ') && !isSafe(c)) {
i++;
}
// Convert them to %XY encoded strings
String unsafe = s.substring(start, i);
byte[] bytes = unsafe.getBytes(encoding);
for (byte aByte : bytes) {
result.append('%');
result.append(hex.charAt(((int) aByte & 0xf0) >> 4));
result.append(hex.charAt((int) aByte & 0x0f));
}
}
start = i;
}
}
/**
* Returns true if the given char is
* either a uppercase or lowercase letter from 'a' till 'z', or a digit
* froim '0' till '9', or one of the characters '-', '_', '.' or ''. Such
* 'safe' character don't have to be url encoded.
*
* @param c the character
* @return true or false
*/
private static boolean isSafe(char c) {
return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))
|| ((c >= '0') && (c <= '9')) || (c == '-') || (c == '_') || (c == '.') || (c == '*'));
}
/**
* Used to convert to hex. We don't use Integer.toHexString, since
* it converts to lower case (and the Sun docs pretty clearly specify
* upper case here), and because it doesn't provide a leading 0.
*/
private static final String hex = "0123456789ABCDEF";
}

View file

@ -0,0 +1,4 @@
/**
* OAI utilities.
*/
package org.xbib.oai.util;

View file

@ -0,0 +1,14 @@
package org.xbib.oai.xml;
import org.xbib.oai.util.RecordHeader;
import org.xml.sax.ContentHandler;
/**
*
*/
public interface MetadataHandler extends ContentHandler {
MetadataHandler setHeader(RecordHeader header);
RecordHeader getHeader();
}

View file

@ -0,0 +1,22 @@
package org.xbib.oai.xml;
import org.xbib.content.xml.util.XMLFilterReader;
import org.xbib.oai.util.RecordHeader;
/**
*
*/
public class SimpleMetadataHandler extends XMLFilterReader implements MetadataHandler {
private RecordHeader header;
public SimpleMetadataHandler setHeader(RecordHeader header) {
this.header = header;
return this;
}
public RecordHeader getHeader() {
return header;
}
}

View file

@ -0,0 +1,297 @@
package org.xbib.oai.xml;
import org.xbib.oai.OAIConstants;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Namespace;
/**
*
*/
public class XmlSimpleMetadataHandler extends SimpleMetadataHandler implements OAIConstants {
private final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
private final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
private List<String> namespaces = new ArrayList<>();
private Stack<Collection<?>> nsStack = new Stack<>();
private Locator locator;
private XMLEventWriter eventWriter;
private Writer writer;
private String id;
private boolean needToCallStartDocument = false;
public XmlSimpleMetadataHandler setWriter(Writer writer) {
this.writer = writer;
try {
outputFactory.setProperty("javax.xml.stream.isRepairingNamespaces", Boolean.TRUE);
this.eventWriter = outputFactory.createXMLEventWriter(writer);
} catch (XMLStreamException e) {
// ignore
}
return this;
}
public Writer getWriter() {
return writer;
}
public XmlSimpleMetadataHandler setEventWriter(XMLEventWriter eventWriter) {
this.eventWriter = eventWriter;
return this;
}
public XMLEventWriter getEventWriter() {
return eventWriter;
}
public String getIdentifier() {
return id;
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
public Location getCurrentLocation() {
if (locator != null) {
return new SAXLocation(locator);
} else {
return null;
}
}
@Override
public void startDocument() throws SAXException {
if (eventWriter == null) {
return;
}
namespaces.clear();
nsStack.clear();
eventFactory.setLocation(getCurrentLocation());
needToCallStartDocument = true;
}
@Override
public void endDocument() throws SAXException {
if (eventWriter == null) {
return;
}
this.id = getHeader().getIdentifier().trim();
try {
eventFactory.setLocation(getCurrentLocation());
eventWriter.add(eventFactory.createEndDocument());
} catch (XMLStreamException e) {
throw new SAXException(e);
}
namespaces.clear();
nsStack.clear();
}
@Override
public void startPrefixMapping(String prefix, String namespaceURI) throws SAXException {
if (eventWriter == null) {
return;
}
if (prefix == null) {
prefix = "";
} else if (prefix.equals("xml")) {
return;
}
if (namespaces == null) {
namespaces = new ArrayList<>();
}
namespaces.add(prefix);
namespaces.add(namespaceURI);
}
@Override
public void endPrefixMapping(String string) throws SAXException {
}
@Override
public void startElement(String uri, String localname, String qname, Attributes attributes) throws SAXException {
if (eventWriter == null) {
return;
}
if (needToCallStartDocument) {
try {
eventWriter.add(eventFactory.createStartDocument());
} catch (XMLStreamException e) {
// is thrown because of document encoding - commented out
//throw new SAXException(e);
}
needToCallStartDocument = false;
}
Collection<?>[] events = {null, null};
createStartEvents(attributes, events);
nsStack.add(events[0]);
try {
String[] q = {null, null};
parseQName(qname, q);
eventFactory.setLocation(getCurrentLocation());
eventWriter.add(eventFactory.createStartElement(q[0], uri,
q[1], events[1].iterator(), events[0].iterator()));
} catch (XMLStreamException e) {
throw new SAXException(e);
}
}
@Override
public void endElement(String uri, String localname, String qname) throws SAXException {
if (eventWriter == null) {
return;
}
String[] q = {null, null};
parseQName(qname, q);
Collection<?> nsList = nsStack.remove(nsStack.size() - 1);
Iterator<?> nsIter = nsList.iterator();
try {
eventFactory.setLocation(getCurrentLocation());
eventWriter.add(eventFactory.createEndElement(q[0], uri, q[1], nsIter));
} catch (XMLStreamException e) {
throw new SAXException(e);
}
}
@Override
public void characters(char[] chars, int i, int i1) throws SAXException {
if (eventWriter == null) {
return;
}
try {
eventFactory.setLocation(getCurrentLocation());
eventWriter.add(eventFactory.createCharacters(new String(chars, i, i1)));
} catch (XMLStreamException e) {
throw new SAXException(e);
}
}
private void createStartEvents(Attributes attributes, Collection<?>[] events) {
Map<String, Namespace> nsMap = null;
List<Attribute> attrs = null;
if (namespaces != null) {
final int nDecls = namespaces.size();
for (int i = 0; i < nDecls; i++) {
final String prefix = namespaces.get(i);
String uri = namespaces.get(i++);
Namespace ns = createNamespace(prefix, uri);
if (nsMap == null) {
nsMap = new HashMap<>();
}
nsMap.put(prefix, ns);
}
}
String[] qname = {null, null};
for (int i = 0, s = attributes.getLength(); i < s; i++) {
parseQName(attributes.getQName(i), qname);
String attrPrefix = qname[0];
String attrLocal = qname[1];
String attrQName = attributes.getQName(i);
String attrValue = attributes.getValue(i);
String attrURI = attributes.getURI(i);
if ("xmlns".equals(attrQName) || "xmlns".equals(attrPrefix)) {
if (!attrValue.isEmpty() && nsMap != null && !nsMap.containsKey(attrPrefix)) {
Namespace ns = createNamespace(attrPrefix, attrValue);
nsMap = new HashMap<>();
nsMap.put(attrPrefix, ns);
}
} else {
Attribute attribute;
if (attrPrefix.length() > 0 && !attrValue.isEmpty()) {
attribute = eventFactory.createAttribute(attrPrefix, attrURI, attrLocal, attrValue);
} else {
attribute = eventFactory.createAttribute(attrLocal, attrValue);
}
if (attrs == null) {
attrs = new ArrayList<>();
}
attrs.add(attribute);
}
}
events[0] = nsMap == null ? Collections.EMPTY_LIST : nsMap.values();
events[1] = attrs == null ? Collections.EMPTY_LIST : attrs;
}
private void parseQName(String qName, String[] results) {
String prefix, local;
int idx = qName.indexOf(':');
if (idx >= 0) {
prefix = qName.substring(0, idx);
local = qName.substring(idx + 1);
} else {
prefix = "";
local = qName;
}
results[0] = prefix;
results[1] = local;
}
private Namespace createNamespace(String prefix, String uri) {
if (prefix == null || prefix.length() == 0) {
return eventFactory.createNamespace(uri);
} else {
return eventFactory.createNamespace(prefix, uri);
}
}
private static final class SAXLocation implements Location {
private int lineNumber;
private int columnNumber;
private String publicId;
private String systemId;
private SAXLocation(Locator locator) {
lineNumber = locator.getLineNumber();
columnNumber = locator.getColumnNumber();
publicId = locator.getPublicId();
systemId = locator.getSystemId();
}
public int getLineNumber() {
return lineNumber;
}
public int getColumnNumber() {
return columnNumber;
}
public int getCharacterOffset() {
return -1;
}
public String getPublicId() {
return publicId;
}
public String getSystemId() {
return systemId;
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for OAI XML processing.
*/
package org.xbib.oai.xml;

4
oai-server/build.gradle Normal file
View file

@ -0,0 +1,4 @@
dependencies {
compile project(':oai-common')
compile "org.xbib.helianthus:helianthus-server:1.0.3"
}

View file

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<property name="groups" value="com,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>

View file

@ -0,0 +1,120 @@
package org.xbib.oai.server;
import org.xbib.oai.OAISession;
import org.xbib.oai.exceptions.OAIException;
import org.xbib.oai.server.getrecord.GetRecordServerRequest;
import org.xbib.oai.server.getrecord.GetRecordServerResponse;
import org.xbib.oai.server.identify.IdentifyServerRequest;
import org.xbib.oai.server.identify.IdentifyServerResponse;
import org.xbib.oai.server.listidentifiers.ListIdentifiersServerRequest;
import org.xbib.oai.server.listidentifiers.ListIdentifiersServerResponse;
import org.xbib.oai.server.listmetadataformats.ListMetadataFormatsServerRequest;
import org.xbib.oai.server.listmetadataformats.ListMetadataFormatsServerResponse;
import org.xbib.oai.server.listrecords.ListRecordsServerRequest;
import org.xbib.oai.server.listrecords.ListRecordsServerResponse;
import org.xbib.oai.server.listsets.ListSetsServerRequest;
import org.xbib.oai.server.listsets.ListSetsServerResponse;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Date;
/**
* OAI server.
*/
public interface OAIServer {
URL getURL();
OAISession newSession() throws URISyntaxException;
/**
* This verb is used to retrieve information about a repository.
* Some of the information returned is required as part of the OAI-PMH.
* Repositories may also employ the Identify verb to return additional
* descriptive information.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void identify(IdentifyServerRequest request, IdentifyServerResponse response) throws OAIException;
/**
* This verb is an abbreviated form of ListRecords, retrieving only
* headers rather than records. Optional arguments permit selective
* harvesting of headers based on set membership and/or datestamp.
* Depending on the repository's support for deletions, a returned
* header may have a status attribute of "deleted" if a record
* matching the arguments specified in the request has been deleted.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void listIdentifiers(ListIdentifiersServerRequest request, ListIdentifiersServerResponse response) throws OAIException;
/**
* This verb is used to retrieve the metadata formats available
* from a repository. An optional argument restricts the request
* to the formats available for a specific item.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void listMetadataFormats(ListMetadataFormatsServerRequest request, ListMetadataFormatsServerResponse response)
throws OAIException;
/**
* This verb is used to retrieve the set structure of a repository,
* useful for selective harvesting.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void listSets(ListSetsServerRequest request, ListSetsServerResponse response) throws OAIException;
/**
* This verb is used to harvest records from a repository.
* Optional arguments permit selective harvesting of records based on
* set membership and/or datestamp. Depending on the repository's
* support for deletions, a returned header may have a status
* attribute of "deleted" if a record matching the arguments
* specified in the request has been deleted. No metadata
* will be present for records with deleted status.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void listRecords(ListRecordsServerRequest request, ListRecordsServerResponse response) throws OAIException;
/**
* This verb is used to retrieve an individual metadata record from
* a repository. Required arguments specify the identifier of the item
* from which the record is requested and the format of the metadata
* that should be included in the record. Depending on the level at
* which a repository tracks deletions, a header with a "deleted" value
* for the status attribute may be returned, in case the metadata format
* specified by the metadataPrefix is no longer available from the
* repository or from the specified item.
* @param request request
* @param response response
* @throws OAIException if verb fails
*/
void getRecord(GetRecordServerRequest request, GetRecordServerResponse response) throws OAIException;
Date getLastModified();
String getRepositoryName();
URL getBaseURL();
String getProtocolVersion();
String getAdminEmail();
String getEarliestDatestamp();
String getDeletedRecord();
String getGranularity();
}

View file

@ -0,0 +1,58 @@
package org.xbib.oai.server;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
/**
*
*/
public class OAIServiceFactory {
private static final Map<URL, OAIServer> services = new HashMap<>();
private static final OAIServiceFactory instance = new OAIServiceFactory();
private OAIServiceFactory() {
ServiceLoader<OAIServer> loader = ServiceLoader.load(OAIServer.class);
for (OAIServer service : loader) {
if (!services.containsKey(service.getURL())) {
services.put(service.getURL(), service);
}
}
}
public static OAIServiceFactory getInstance() {
return instance;
}
public static OAIServer getDefaultService() {
return services.isEmpty() ? null : services.entrySet().iterator().next().getValue();
}
public static OAIServer getService(URL url) {
if (services.containsKey(url)) {
return services.get(url);
}
throw new IllegalArgumentException("OAI service " + url + " not found in " + services);
}
public static OAIServer getService(String name) {
Properties properties = new Properties();
InputStream in = instance.getClass().getResourceAsStream("/org/xbib/oai/service/" + name + ".properties");
if (in != null) {
try {
properties.load(in);
} catch (IOException ex) {
// ignore
}
} else {
throw new IllegalArgumentException("service " + name + " not found");
}
return new PropertiesOAIServer(properties);
}
}

View file

@ -0,0 +1,144 @@
package org.xbib.oai.server;
import org.xbib.oai.OAISession;
import org.xbib.oai.exceptions.OAIException;
import org.xbib.oai.server.getrecord.GetRecordServerRequest;
import org.xbib.oai.server.getrecord.GetRecordServerResponse;
import org.xbib.oai.server.identify.IdentifyServerRequest;
import org.xbib.oai.server.identify.IdentifyServerResponse;
import org.xbib.oai.server.listidentifiers.ListIdentifiersServerRequest;
import org.xbib.oai.server.listidentifiers.ListIdentifiersServerResponse;
import org.xbib.oai.server.listmetadataformats.ListMetadataFormatsServerRequest;
import org.xbib.oai.server.listmetadataformats.ListMetadataFormatsServerResponse;
import org.xbib.oai.server.listrecords.ListRecordsServerRequest;
import org.xbib.oai.server.listrecords.ListRecordsServerResponse;
import org.xbib.oai.server.listsets.ListSetsServerRequest;
import org.xbib.oai.server.listsets.ListSetsServerResponse;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Properties;
/**
*
*/
public class PropertiesOAIServer implements OAIServer {
public static final String ADAPTER_URI = "uri";
public static final String STYLESHEET = "stylesheet";
public static final String REPOSITORY_NAME = "identify.repositoryName";
public static final String BASE_URL = "identify.baseURL";
public static final String PROTOCOL_VERSION = "identify.protocolVersion";
public static final String ADMIN_EMAIL = "identify.adminEmail";
public static final String EARLIEST_DATESTAMP = "identify.earliestDatestamp";
public static final String DELETED_RECORD = "identify.deletedRecord";
public static final String GRANULARITY = "identify.granularity";
private Properties properties;
public PropertiesOAIServer(Properties properties) {
this.properties = properties;
}
@Override
public URL getURL() {
try {
return new URL(properties.getProperty(ADAPTER_URI).trim());
} catch (MalformedURLException e) {
//
}
return null;
}
public String getStylesheet() {
return properties.getProperty(STYLESHEET);
}
@Override
public String getRepositoryName() {
return properties.getProperty(REPOSITORY_NAME);
}
@Override
public URL getBaseURL() {
try {
return new URL(properties.getProperty(BASE_URL));
} catch (MalformedURLException e) {
return null;
}
}
@Override
public String getProtocolVersion() {
return properties.getProperty(PROTOCOL_VERSION);
}
@Override
public String getAdminEmail() {
return properties.getProperty(ADMIN_EMAIL);
}
@Override
public String getEarliestDatestamp() {
return properties.getProperty(EARLIEST_DATESTAMP);
}
@Override
public String getDeletedRecord() {
return properties.getProperty(DELETED_RECORD);
}
@Override
public String getGranularity() {
return properties.getProperty(GRANULARITY);
}
@Override
public OAISession newSession() {
return null;
}
@Override
public Date getLastModified() {
return null;
}
@Override
public void identify(IdentifyServerRequest request, IdentifyServerResponse response)
throws OAIException {
}
@Override
public void listMetadataFormats(ListMetadataFormatsServerRequest request, ListMetadataFormatsServerResponse response)
throws OAIException {
}
@Override
public void listSets(ListSetsServerRequest request, ListSetsServerResponse response)
throws OAIException {
}
@Override
public void listIdentifiers(ListIdentifiersServerRequest request, ListIdentifiersServerResponse response)
throws OAIException {
}
@Override
public void listRecords(ListRecordsServerRequest request, ListRecordsServerResponse response)
throws OAIException {
}
@Override
public void getRecord(GetRecordServerRequest request, GetRecordServerResponse response)
throws OAIException {
}
}

View file

@ -0,0 +1,108 @@
package org.xbib.oai.server;
import org.xbib.oai.OAIConstants;
import org.xbib.oai.OAIRequest;
import org.xbib.oai.util.ResumptionToken;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public abstract class ServerOAIRequest implements OAIRequest {
private String path;
private Map<String, String> parameters;
private ResumptionToken<?> token;
private String set;
private String metadataPrefix;
private Instant from;
private Instant until;
private boolean retry;
protected ServerOAIRequest() {
this.parameters = new HashMap<>();
}
@Override
public void setSet(String set) {
this.set = set;
parameters.put(OAIConstants.SET_PARAMETER, set);
}
public String getSet() {
return set;
}
@Override
public void setMetadataPrefix(String prefix) {
this.metadataPrefix = prefix;
parameters.put(OAIConstants.METADATA_PREFIX_PARAMETER, prefix);
}
public String getMetadataPrefix() {
return metadataPrefix;
}
@Override
public void setFrom(Instant from) {
this.from = from;
parameters.put(OAIConstants.FROM_PARAMETER, from.toString());
}
public Instant getFrom() {
return from;
}
@Override
public void setUntil(Instant until) {
this.until = until;
parameters.put(OAIConstants.UNTIL_PARAMETER, until.toString());
}
public Instant getUntil() {
return until;
}
@Override
public void setResumptionToken(ResumptionToken<?> token) {
this.token = token;
if (token != null) {
parameters.put(OAIConstants.RESUMPTION_TOKEN_PARAMETER, token.toString());
}
}
@Override
public ResumptionToken<?> getResumptionToken() {
return token;
}
public void setRetry(boolean retry) {
this.retry = retry;
}
public boolean isRetry() {
return retry;
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return path;
}
public Map<String, String> getParameterMap() {
return parameters;
}
}

View file

@ -0,0 +1,36 @@
package org.xbib.oai.server;
import org.xbib.oai.OAIResponse;
import java.io.IOException;
import java.io.Writer;
import javax.xml.stream.util.XMLEventConsumer;
/**
* Default OAI response.
*/
public class ServerOAIResponse implements OAIResponse {
private String format;
private XMLEventConsumer consumer;
public String getOutputFormat() {
return format;
}
@Override
public void to(Writer writer) throws IOException {
}
public ServerOAIResponse setConsumer(XMLEventConsumer consumer) {
this.consumer = consumer;
return this;
}
public XMLEventConsumer getConsumer() {
return consumer;
}
}

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.getrecord;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class GetRecordServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,9 @@
package org.xbib.oai.server.getrecord;
import org.xbib.oai.server.ServerOAIResponse;
/**
*
*/
public class GetRecordServerResponse extends ServerOAIResponse {
}

View file

@ -0,0 +1,4 @@
/**
* OAI get record.
*/
package org.xbib.oai.server.getrecord;

View file

@ -0,0 +1,9 @@
package org.xbib.oai.server.identify;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class IdentifyServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,101 @@
package org.xbib.oai.server.identify;
import org.xbib.oai.server.ServerOAIResponse;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
*
*/
public class IdentifyServerResponse extends ServerOAIResponse {
private String repositoryName;
private URL baseURL;
private String protocolVersion;
private List<String> adminEmails = new ArrayList<>();
private Date earliestDatestamp;
private String deletedRecord;
private String granularity;
private String compression;
@Override
public void to(Writer writer) throws IOException {
}
public void setRepositoryName(String repositoryName) {
this.repositoryName = repositoryName;
}
public String getRepositoryName() {
return repositoryName;
}
public void setBaseURL(URL url) {
this.baseURL = url;
}
public URL getBaseURL() {
return baseURL;
}
public void setProtocolVersion(String protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getProtocolVersion() {
return protocolVersion;
}
public void addAdminEmail(String email) {
adminEmails.add(email);
}
public List<String> getAdminEmails() {
return adminEmails;
}
public void setEarliestDatestamp(Date earliestDatestamp) {
this.earliestDatestamp = earliestDatestamp;
}
public Date getEarliestDatestamp() {
return earliestDatestamp;
}
public void setDeletedRecord(String deletedRecord) {
this.deletedRecord = deletedRecord;
}
public String getDeleteRecord() {
return deletedRecord;
}
public void setGranularity(String granularity) {
this.granularity = granularity;
}
public String getGranularity() {
return granularity;
}
public void setCompression(String compression) {
this.compression = compression;
}
public String getCompression() {
return compression;
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI identify verb.
*/
package org.xbib.oai.server.identify;

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listidentifiers;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class ListIdentifiersServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listidentifiers;
import org.xbib.oai.server.ServerOAIResponse;
/**
*
*/
public class ListIdentifiersServerResponse extends ServerOAIResponse {
}

View file

@ -0,0 +1,4 @@
/**
* OAI list identifiers verb.
*/
package org.xbib.oai.server.listidentifiers;

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listmetadataformats;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class ListMetadataFormatsServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listmetadataformats;
import org.xbib.oai.server.ServerOAIResponse;
/**
*
*/
public class ListMetadataFormatsServerResponse extends ServerOAIResponse {
}

View file

@ -0,0 +1,4 @@
/**
* OAI list metadata formats verb.
*/
package org.xbib.oai.server.listmetadataformats;

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listrecords;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class ListRecordsServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,58 @@
package org.xbib.oai.server.listrecords;
import org.xbib.oai.server.ServerOAIResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class ListRecordsServerResponse extends ServerOAIResponse {
private static final Logger logger = Logger.getLogger(ListRecordsServerResponse.class.getName());
private String error;
private Date date;
private long expire;
public void setError(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setDate(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
public void setExpire(long expire) {
this.expire = expire;
}
@Override
public void to(Writer writer) throws IOException {
try {
if (this.expire > 0L) {
logger.log(Level.INFO, "waiting for {} seconds (retry-after)", expire);
Thread.sleep(1000 * expire);
this.expire = 0L;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.log(Level.WARNING, "interrupted");
}
}
}

View file

@ -0,0 +1,4 @@
/**
* OAI list records verb.
*/
package org.xbib.oai.server.listrecords;

View file

@ -0,0 +1,10 @@
package org.xbib.oai.server.listsets;
import org.xbib.oai.server.ServerOAIRequest;
/**
*
*/
public class ListSetsServerRequest extends ServerOAIRequest {
}

View file

@ -0,0 +1,9 @@
package org.xbib.oai.server.listsets;
import org.xbib.oai.server.ServerOAIResponse;
/**
*
*/
public class ListSetsServerResponse extends ServerOAIResponse {
}

View file

@ -0,0 +1,4 @@
/**
* OAI list sets verb.
*/
package org.xbib.oai.server.listsets;

View file

@ -0,0 +1,4 @@
/**
* Classes for OAI server.
*/
package org.xbib.oai.server;

Some files were not shown because too many files have changed in this diff Show more