Compare commits
10 commits
560bd890f0
...
ab20035583
Author | SHA1 | Date | |
---|---|---|---|
ab20035583 | |||
47972552c8 | |||
19f02c350d | |||
9e5d8e6775 | |||
ea79e7afd6 | |||
f81b656f25 | |||
8d3455a547 | |||
d3a77846e4 | |||
3459514a9a | |||
e1369a2413 |
107 changed files with 2284 additions and 628 deletions
|
@ -1,4 +0,0 @@
|
|||
language: java
|
||||
jdk:
|
||||
- openjdk11
|
||||
|
27
build.gradle
27
build.gradle
|
@ -1,11 +1,13 @@
|
|||
|
||||
plugins {
|
||||
id "de.marcphilipp.nexus-publish" version "0.4.0"
|
||||
id "io.codearte.nexus-staging" version "0.21.1"
|
||||
id 'maven-publish'
|
||||
id 'signing'
|
||||
id "io.github.gradle-nexus.publish-plugin" version "1.3.0"
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "${project.property('gradle.wrapper.version')}"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
gradleVersion = libs.versions.gradle.get()
|
||||
distributionType = Wrapper.DistributionType.BIN
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -13,10 +15,10 @@ ext {
|
|||
name = 'oai'
|
||||
description = 'Open Archive Initiative library for Java'
|
||||
inceptionYear = '2016'
|
||||
url = 'https://github.com/' + user + '/' + name
|
||||
scmUrl = 'https://github.com/' + user + '/' + name
|
||||
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
|
||||
scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git'
|
||||
url = 'https://xbib.org/' + user + '/' + name
|
||||
scmUrl = 'https://xbib.org/' + user + '/' + name
|
||||
scmConnection = 'scm:git:git://xbib.org/' + user + '/' + name + '.git'
|
||||
scmDeveloperConnection = 'scm:git:ssh://forgejo@xbib.org:' + user + '/' + name + '.git'
|
||||
issueManagementSystem = 'Github'
|
||||
issueManagementUrl = ext.scmUrl + '/issues'
|
||||
licenseName = 'The Apache License, Version 2.0'
|
||||
|
@ -24,11 +26,10 @@ ext {
|
|||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java-library'
|
||||
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||
apply from: rootProject.file('gradle/repositories/maven.gradle')
|
||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
||||
apply from: rootProject.file('gradle/publish/maven.gradle')
|
||||
}
|
||||
|
||||
apply from: rootProject.file('gradle/publishing/sonatype.gradle')
|
||||
apply from: rootProject.file('gradle/publish/sonatype.gradle')
|
||||
apply from: rootProject.file('gradle/publish/forgejo.gradle')
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||
<suppressions>
|
||||
<suppress checks="." files="[\\/]generated-src[\\/].*\.java$"/>
|
||||
</suppressions>
|
|
@ -1,8 +1,3 @@
|
|||
group = org.xbib
|
||||
name = oai
|
||||
version = 2.5.3
|
||||
|
||||
gradle.wrapper.version = 6.6.1
|
||||
xbib-content.version = 2.6.2
|
||||
xbib-marc.version = 2.4.0
|
||||
xbib-bibliographic-character-sets.version = 2.0.0
|
||||
version = 4.0.0
|
||||
|
|
|
@ -1,35 +1,47 @@
|
|||
|
||||
apply plugin: 'java-library'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
modularity.inferModulePath.set(true)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Implementation-Version': project.version)
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier 'sources'
|
||||
from sourceSets.main.allSource
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
doFirst {
|
||||
options.fork = true
|
||||
options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US']
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs.add('-Xlint:all')
|
||||
// enforce presence of module-info.java
|
||||
options.compilerArgs.add("--module-version")
|
||||
options.compilerArgs.add(project.version as String)
|
||||
options.compilerArgs.add("--module-path")
|
||||
options.compilerArgs.add(classpath.asPath)
|
||||
classpath = files()
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier 'javadoc'
|
||||
tasks.withType(Javadoc).configureEach {
|
||||
doFirst {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar, javadocJar
|
||||
tasks.withType(JavaExec).configureEach {
|
||||
doFirst {
|
||||
jvmArguments.add("--module-path")
|
||||
jvmArguments.add(classpath.asPath)
|
||||
classpath = files()
|
||||
}
|
||||
}
|
|
@ -1,14 +1,5 @@
|
|||
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||
|
||||
configurations {
|
||||
asciidoclet
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||
}
|
||||
|
||||
|
||||
asciidoctor {
|
||||
backends 'html5'
|
||||
outputDir = file("${rootProject.projectDir}/docs")
|
||||
|
@ -26,30 +17,3 @@ asciidoctor {
|
|||
imagesdir: 'img',
|
||||
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
|
||||
}
|
||||
|
||||
|
||||
/*javadoc {
|
||||
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||
//options.overview = "src/docs/asciidoclet/overview.adoc"
|
||||
options.addStringOption "-base-dir", "${projectDir}"
|
||||
options.addStringOption "-attribute",
|
||||
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||
configure(options) {
|
||||
noTimestamp = true
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/*javadoc {
|
||||
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||
options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc"
|
||||
options.addStringOption "-base-dir", "${projectDir}"
|
||||
options.addStringOption "-attribute",
|
||||
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||
options.destinationDirectory(file("${projectDir}/docs/javadoc"))
|
||||
configure(options) {
|
||||
noTimestamp = true
|
||||
}
|
||||
}*/
|
||||
|
|
16
gradle/publish/forgejo.gradle
Normal file
16
gradle/publish/forgejo.gradle
Normal file
|
@ -0,0 +1,16 @@
|
|||
if (project.hasProperty('forgeJoToken')) {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://xbib.org/api/packages/xbib/maven'
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = "token ${project.property('forgeJoToken')}"
|
||||
}
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
gradle/publish/ivy.gradle
Normal file
27
gradle/publish/ivy.gradle
Normal file
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'ivy-publish'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
ivy {
|
||||
url = "https://xbib.org/repo"
|
||||
}
|
||||
}
|
||||
publications {
|
||||
ivy(IvyPublication) {
|
||||
from components.java
|
||||
descriptor {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
author {
|
||||
name = 'Jörg Prante'
|
||||
url = 'http://example.com/users/jane'
|
||||
}
|
||||
descriptor.description {
|
||||
text = rootProject.ext.description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
|
||||
apply plugin: "de.marcphilipp.nexus-publish"
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
"${project.name}"(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
pom {
|
||||
artifactId = project.name
|
||||
name = project.name
|
||||
description = rootProject.ext.description
|
||||
url = rootProject.ext.url
|
||||
|
@ -49,16 +46,6 @@ publishing {
|
|||
if (project.hasProperty("signing.keyId")) {
|
||||
apply plugin: 'signing'
|
||||
signing {
|
||||
sign publishing.publications.mavenJava
|
||||
}
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
sign publishing.publications."${project.name}"
|
||||
}
|
||||
}
|
11
gradle/publish/sonatype.gradle
Normal file
11
gradle/publish/sonatype.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
|
||||
apply plugin: 'io.codearte.nexus-staging'
|
||||
|
||||
nexusStaging {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
19
gradle/quality/checkstyle.gradle
Normal file
19
gradle/quality/checkstyle.gradle
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
configFile = rootProject.file('gradle/quality/checkstyle.xml')
|
||||
ignoreFailures = true
|
||||
showViolations = true
|
||||
checkstyleMain {
|
||||
source = sourceSets.main.allSource
|
||||
}
|
||||
}
|
|
@ -9,6 +9,10 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||
|
||||
<module name="Checker">
|
||||
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
|
||||
</module>
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<!-- Checks that there are no tab characters in the file.
|
||||
-->
|
||||
|
@ -56,10 +60,19 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||
<!-- 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"/>
|
||||
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
|
||||
<property name="separated" value="false"/>
|
||||
<property name="groups" value="*"/>
|
||||
<!-- <property name="option" value="above"/> -->
|
||||
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="CustomImportOrder">
|
||||
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
|
||||
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
|
||||
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
<property name="separateLineBetweenGroups" value="false"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
@ -71,14 +84,10 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||
<module name="JavadocMethod">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="accessModifiers" 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">
|
||||
|
@ -184,21 +193,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||
|
||||
-->
|
||||
|
||||
<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 ('{'). -->
|
||||
|
@ -319,5 +313,21 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
<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>
|
||||
|
11
gradle/quality/cyclonedx.gradle
Normal file
11
gradle/quality/cyclonedx.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
cyclonedxBom {
|
||||
includeConfigs = [ 'runtimeClasspath' ]
|
||||
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
|
||||
projectType = "library"
|
||||
schemaVersion = "1.4"
|
||||
destination = file("build/reports")
|
||||
outputName = "bom"
|
||||
outputFormat = "json"
|
||||
includeBomSerialNumber = true
|
||||
componentVersion = "2.0.0"
|
||||
}
|
17
gradle/quality/pmd.gradle
Normal file
17
gradle/quality/pmd.gradle
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
apply plugin: 'pmd'
|
||||
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
pmd {
|
||||
ignoreFailures = true
|
||||
consoleOutput = false
|
||||
toolVersion = "6.51.0"
|
||||
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
|
||||
}
|
|
@ -48,6 +48,7 @@ public abstract class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="AccessorClassGeneration"
|
||||
language="java"
|
||||
since="1.04"
|
||||
maximumLanguageVersion="10"
|
||||
message="Avoid instantiation through private constructors from outside of the constructor's class."
|
||||
|
@ -109,6 +110,7 @@ public class OuterClass {
|
|||
</rule>
|
||||
|
||||
<rule name="ArrayIsStoredDirectly"
|
||||
language="java"
|
||||
since="2.2"
|
||||
message="The user-supplied array ''{0}'' is stored directly."
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.ArrayIsStoredDirectlyRule"
|
||||
|
@ -168,6 +170,7 @@ class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="AvoidReassigningLoopVariables"
|
||||
language="java"
|
||||
since="6.11.0"
|
||||
message="Avoid reassigning the loop control variable ''{0}''"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.AvoidReassigningLoopVariablesRule"
|
||||
|
@ -216,6 +219,7 @@ public class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="AvoidReassigningParameters"
|
||||
language="java"
|
||||
since="1.0"
|
||||
message="Avoid reassigning parameters such as ''{0}''"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.AvoidReassigningParametersRule"
|
||||
|
@ -265,6 +269,7 @@ public class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="AvoidUsingHardCodedIP"
|
||||
language="java"
|
||||
since="4.1"
|
||||
message="Do not hard code the IP address ${variableName}"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.AvoidUsingHardCodedIPRule"
|
||||
|
@ -493,7 +498,7 @@ for (int i = 0, j = 0; i < 10; i++, j += 2) {
|
|||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='suite']]
|
||||
//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Name='suite']]
|
||||
[MethodDeclaration/ResultType/Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('junit.framework.Test')]]
|
||||
[not(MethodDeclaration/Block//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.JUnit4TestAdapter')])]
|
||||
]]>
|
||||
|
@ -535,7 +540,7 @@ public class GoodTest {
|
|||
<value>
|
||||
<![CDATA[
|
||||
//ClassOrInterfaceBodyDeclaration
|
||||
[MethodDeclaration/MethodDeclarator[@Image='tearDown']]
|
||||
[MethodDeclaration/MethodDeclarator[@Name='tearDown']]
|
||||
[count(Annotation//Name[
|
||||
pmd-java:typeIs('org.junit.After')
|
||||
or pmd-java:typeIs('org.junit.jupiter.api.AfterEach')
|
||||
|
@ -579,7 +584,7 @@ public class MyTest2 {
|
|||
<value>
|
||||
<![CDATA[
|
||||
//ClassOrInterfaceBodyDeclaration
|
||||
[MethodDeclaration/MethodDeclarator[@Image='setUp']]
|
||||
[MethodDeclaration/MethodDeclarator[@Name='setUp']]
|
||||
[count(Annotation//Name[
|
||||
pmd-java:typeIs('org.junit.Before')
|
||||
or pmd-java:typeIs('org.junit.jupiter.api.BeforeEach')
|
||||
|
@ -623,10 +628,10 @@ public class MyTest2 {
|
|||
<value>
|
||||
<![CDATA[
|
||||
//ClassOrInterfaceDeclaration[
|
||||
matches(@Image, $testClassPattern)
|
||||
matches(@SimpleName, $testClassPattern)
|
||||
or ExtendsList/ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')]]
|
||||
|
||||
/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[MethodDeclaration[@Public=true()]/MethodDeclarator[starts-with(@Image, 'test')]]
|
||||
/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[MethodDeclaration[@Public=true()]/MethodDeclarator[starts-with(@Name, 'test')]]
|
||||
[not(Annotation//Name[
|
||||
pmd-java:typeIs('org.junit.Test')
|
||||
or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest')
|
||||
|
@ -656,6 +661,7 @@ public class MyTest {
|
|||
</rule>
|
||||
|
||||
<rule name="JUnitAssertionsShouldIncludeMessage"
|
||||
language="java"
|
||||
since="1.04"
|
||||
message="JUnit assertions should include a message"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitAssertionsShouldIncludeMessageRule"
|
||||
|
@ -734,6 +740,7 @@ public class MyTestCase extends TestCase {
|
|||
</rule>
|
||||
|
||||
<rule name="JUnitTestsShouldIncludeAssert"
|
||||
language="java"
|
||||
since="2.0"
|
||||
message="JUnit tests should include assert() or fail()"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitTestsShouldIncludeAssertRule"
|
||||
|
@ -758,6 +765,7 @@ public class Foo extends TestCase {
|
|||
</rule>
|
||||
|
||||
<rule name="JUnitUseExpected"
|
||||
language="java"
|
||||
since="4.0"
|
||||
message="In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitUseExpectedRule"
|
||||
|
@ -788,6 +796,7 @@ public class MyTest {
|
|||
</rule>
|
||||
|
||||
<rule name="LooseCoupling"
|
||||
language="java"
|
||||
since="0.7"
|
||||
message="Avoid using implementation types like ''{0}''; use the interface instead"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.LooseCouplingRule"
|
||||
|
@ -824,6 +833,7 @@ public class Bar {
|
|||
</rule>
|
||||
|
||||
<rule name="MethodReturnsInternalArray"
|
||||
language="java"
|
||||
since="2.2"
|
||||
message="Returning ''{0}'' may expose an internal array."
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.MethodReturnsInternalArrayRule"
|
||||
|
@ -848,6 +858,7 @@ public class SecureSystem {
|
|||
|
||||
|
||||
<rule name="MissingOverride"
|
||||
language="java"
|
||||
since="6.2.0"
|
||||
minimumLanguageVersion="1.5"
|
||||
message="The method ''{0}'' is missing an @Override annotation."
|
||||
|
@ -1000,6 +1011,7 @@ class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="PreserveStackTrace"
|
||||
language="java"
|
||||
since="3.7"
|
||||
message="New exception is thrown in catch block, original stack trace may be lost"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.PreserveStackTraceRule"
|
||||
|
@ -1217,6 +1229,7 @@ public class Foo {
|
|||
</rule>
|
||||
|
||||
<rule name="UnusedImports"
|
||||
language="java"
|
||||
since="1.0"
|
||||
message="Avoid unused imports such as ''{0}''"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedImportsRule"
|
||||
|
@ -1489,6 +1502,7 @@ public class MyTestCase extends TestCase {
|
|||
</rule>
|
||||
|
||||
<rule name="UseCollectionIsEmpty"
|
||||
language="java"
|
||||
since="3.9"
|
||||
message="Substitute calls to size() == 0 (or size() != 0, size() > 0, size() < 1) with calls to isEmpty()"
|
||||
class="net.sourceforge.pmd.lang.java.rule.bestpractices.UseCollectionIsEmptyRule"
|
||||
|
@ -1546,7 +1560,7 @@ public class Foo {
|
|||
][
|
||||
pmd-java:typeIs('java.lang.AutoCloseable')
|
||||
or
|
||||
../../PrimarySuffix/Arguments[@ArgumentCount = 1]//PrimaryPrefix[pmd-java:typeIs('java.lang.AutoCloseable')]
|
||||
../../PrimarySuffix/Arguments[@Size = 1]//PrimaryPrefix[pmd-java:typeIs('java.lang.AutoCloseable')]
|
||||
]]
|
||||
]]>
|
||||
</value>
|
|
@ -1,6 +1,3 @@
|
|||
#
|
||||
# BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
#
|
||||
|
||||
rulesets.filenames=\
|
||||
category/java/bestpractices.xml,\
|
|
@ -760,7 +760,7 @@ public class HelloWorldBean {
|
|||
<![CDATA[
|
||||
public class Foo {
|
||||
void bar() {
|
||||
for (;true;) true; // No Init or Update part, may as well be: while (true)
|
||||
for (;true;) true; // No Init or Update message, may as well be: while (true)
|
||||
}
|
||||
}
|
||||
]]>
|
|
@ -158,7 +158,7 @@ public class A {
|
|||
message="Avoid using a branching statement as the last in a loop."
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop">
|
||||
<description>
|
||||
Using a branching statement as the last part of a loop may be a bug, and/or is confusing.
|
||||
Using a branching statement as the last message of a loop may be a bug, and/or is confusing.
|
||||
Ensure that the usage is not a bug, or consider using another approach.
|
||||
</description>
|
||||
<priority>2</priority>
|
||||
|
@ -1554,7 +1554,7 @@ public class Foo {
|
|||
<rule name="EmptyStatementNotInLoop"
|
||||
language="java"
|
||||
since="1.5"
|
||||
message="An empty statement (semicolon) not part of a loop"
|
||||
message="An empty statement (semicolon) not message of a loop"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#emptystatementnotinloop">
|
||||
<description>
|
37
gradle/quality/sonarqube.gradle
Normal file
37
gradle/quality/sonarqube.gradle
Normal file
|
@ -0,0 +1,37 @@
|
|||
|
||||
subprojects {
|
||||
|
||||
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.junit.reportsPath", "build/test-results/test/"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
spotbugs {
|
||||
effort = "max"
|
||||
reportLevel = "low"
|
||||
//includeFilter = file("findbugs-exclude.xml")
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.SpotBugsTask) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
}
|
15
gradle/quality/spotbugs.gradle
Normal file
15
gradle/quality/spotbugs.gradle
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
apply plugin: 'com.github.spotbugs'
|
||||
|
||||
spotbugs {
|
||||
effort = "max"
|
||||
reportLevel = "low"
|
||||
ignoreFailures = true
|
||||
}
|
||||
|
||||
spotbugsMain {
|
||||
reports {
|
||||
xml.getRequired().set(false)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
4
gradle/repositories/maven.gradle
Normal file
4
gradle/repositories/maven.gradle
Normal file
|
@ -0,0 +1,4 @@
|
|||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
|
@ -1,17 +1,30 @@
|
|||
|
||||
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.7.0'
|
||||
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
|
||||
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||
testImplementation testLibs.junit.jupiter.api
|
||||
testImplementation testLibs.junit.jupiter.params
|
||||
testImplementation testLibs.hamcrest
|
||||
testRuntimeOnly testLibs.junit.jupiter.engine
|
||||
testRuntimeOnly testLibs.junit.jupiter.platform.launcher
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
failFast = true
|
||||
failFast = false
|
||||
jvmArgs '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
|
||||
'--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED',
|
||||
'--add-exports=java.base/sun.nio.ch=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.io=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.nio=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.util=ALL-UNNAMED'
|
||||
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
|
||||
systemProperty 'io.netty.tryReflectionSetAccessible', 'false'
|
||||
systemProperty 'io.netty.allocator.typ', 'pooled'
|
||||
systemProperty 'io.netty.noUnsafe', 'true'
|
||||
systemProperty 'io.netty.noKeySetOptimization', 'true'
|
||||
systemProperty 'io.netty.eventLoopThreads', '8'
|
||||
systemProperty 'io.netty.recycler.maxCapacityPerThread', '0'
|
||||
systemProperty 'io.netty.transport.noNative', 'true'
|
||||
testLogging {
|
||||
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||
}
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
294
gradlew
vendored
294
gradlew
vendored
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,67 +17,99 @@
|
|||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
35
gradlew.bat
vendored
35
gradlew.bat
vendored
|
@ -14,7 +14,7 @@
|
|||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
|
@ -25,7 +25,8 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
dependencies {
|
||||
api project(':oai-common')
|
||||
testImplementation "org.xbib:marc:${project.property('xbib-marc.version')}"
|
||||
testImplementation "org.xbib:bibliographic-character-sets:${project.property('xbib-bibliographic-character-sets.version')}"
|
||||
testImplementation libs.marc
|
||||
testImplementation libs.charactersets
|
||||
}
|
||||
|
||||
def patchArgs = ['--patch-module', "org.xbib.oai.client.test=" + sourceSets.test.resources.sourceDirectories.singleFile]
|
||||
|
||||
tasks.named('compileTestJava') {
|
||||
options.compilerArgs += patchArgs
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
jvmArgs += patchArgs
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ module org.xbib.oai.client {
|
|||
exports org.xbib.oai.client.listmetadataformats;
|
||||
exports org.xbib.oai.client.listrecords;
|
||||
exports org.xbib.oai.client.listsets;
|
||||
requires org.xbib.oai;
|
||||
requires org.xbib.net.url;
|
||||
exports org.xbib.oai.client.util;
|
||||
requires transitive org.xbib.oai;
|
||||
requires org.xbib.content.xml;
|
||||
requires java.xml;
|
||||
requires java.logging;
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package org.xbib.oai.client;
|
||||
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.oai.client.getrecord.GetRecordRequest;
|
||||
import org.xbib.oai.OAIConstants;
|
||||
import org.xbib.oai.client.identify.IdentifyRequest;
|
||||
import org.xbib.oai.client.identify.IdentifyResponse;
|
||||
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.listrecords.ListRecordsResponse;
|
||||
import org.xbib.oai.client.listsets.ListSetsRequest;
|
||||
import org.xbib.oai.client.util.UrlBuilder;
|
||||
import org.xbib.oai.exceptions.NoRecordsMatchException;
|
||||
import org.xbib.oai.util.ResumptionToken;
|
||||
import org.xbib.oai.xml.MetadataHandler;
|
||||
|
@ -76,7 +73,7 @@ public class OAIClient {
|
|||
public IdentifyResponse identify() throws IOException, InterruptedException {
|
||||
IdentifyRequest identifyRequest = new IdentifyRequest();
|
||||
IdentifyResponse identifyResponse = new IdentifyResponse();
|
||||
URL.Builder url = URL.from(baseURL).mutator();
|
||||
UrlBuilder url = UrlBuilder.fromUrl(baseURL);
|
||||
identifyRequest.getParams().forEach(url::queryParam);
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url.build().toExternalForm()))
|
||||
|
@ -99,18 +96,12 @@ public class OAIClient {
|
|||
* to the formats available for a specific item.
|
||||
* @return list metadata formats request
|
||||
*/
|
||||
public ListMetadataFormatsRequest newListMetadataFormatsRequest() {
|
||||
return new ListMetadataFormatsRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* This verb is used to retrieve the set structure of a repository,
|
||||
* useful for selective harvesting.
|
||||
* @return list sets request
|
||||
*/
|
||||
public ListSetsRequest newListSetsRequest() {
|
||||
return new ListSetsRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* This verb is an abbreviated form of ListRecords, retrieving only
|
||||
|
@ -122,9 +113,6 @@ public class OAIClient {
|
|||
* @return list identifiers request
|
||||
*
|
||||
*/
|
||||
public ListIdentifiersRequest newListIdentifiersRequest() {
|
||||
return new ListIdentifiersRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* This verb is used to retrieve an individual metadata record from
|
||||
|
@ -137,9 +125,6 @@ public class OAIClient {
|
|||
* repository or from the specified item.
|
||||
* @return get record request
|
||||
*/
|
||||
public GetRecordRequest newGetRecordRequest() {
|
||||
return new GetRecordRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* This verb is used to harvest records from a repository.
|
||||
|
@ -195,10 +180,18 @@ public class OAIClient {
|
|||
listRecordsRequest.addHandler(handler);
|
||||
}
|
||||
ListRecordsResponse listRecordsResponse = new ListRecordsResponse(listRecordsRequest);
|
||||
URL.Builder url = URL.from(baseURL).mutator();
|
||||
UrlBuilder url = UrlBuilder.fromUrl(baseURL);
|
||||
// kind of hacky here - suppress all OAI params if resumption token is present
|
||||
if (listRecordsRequest.getResumptionToken() == null) {
|
||||
listRecordsRequest.getParams().forEach(url::queryParam);
|
||||
} else {
|
||||
url.queryParam(OAIConstants.VERB_PARAMETER, OAIConstants.LIST_RECORDS);
|
||||
url.queryParam(OAIConstants.RESUMPTION_TOKEN_PARAMETER, listRecordsRequest.getResumptionToken().toString());
|
||||
}
|
||||
|
||||
URI uri = URI.create(url.build().toExternalForm());
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url.build().toExternalForm()))
|
||||
.uri(uri)
|
||||
.header("accept", "utf-8")
|
||||
.header("user-agent", userAgent != null ? userAgent : "xbib OAI client")
|
||||
.GET()
|
||||
|
@ -216,7 +209,6 @@ public class OAIClient {
|
|||
byte[] b = httpResponse.body();
|
||||
if (b.length > 0) {
|
||||
consumer.accept(new ByteArrayInputStream(b));
|
||||
logger.log(Level.FINE, "body consumed: " + body);
|
||||
}
|
||||
} else {
|
||||
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||
|
@ -262,81 +254,15 @@ public class OAIClient {
|
|||
}
|
||||
}
|
||||
|
||||
public IdentifyRequest resume(IdentifyRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
IdentifyRequest nextRequest = new IdentifyRequest();
|
||||
nextRequest.setResumptionToken(token);
|
||||
return nextRequest;
|
||||
}
|
||||
|
||||
public ListRecordsRequest resume(ListRecordsRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
ListRecordsRequest nextRequest = new ListRecordsRequest();
|
||||
ListRecordsRequest nextRequest = token != null ? new ListRecordsRequest() : request;
|
||||
if (token != null) {
|
||||
nextRequest.setResumptionToken(token);
|
||||
}
|
||||
if (request.isRetry()) {
|
||||
nextRequest.setRetry(false);
|
||||
return nextRequest;
|
||||
}
|
||||
|
||||
public ListIdentifiersRequest resume(ListIdentifiersRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
ListIdentifiersRequest nextRequest = newListIdentifiersRequest();
|
||||
nextRequest.setResumptionToken(token);
|
||||
return nextRequest;
|
||||
}
|
||||
|
||||
public ListMetadataFormatsRequest resume(ListMetadataFormatsRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
ListMetadataFormatsRequest nextRequest = newListMetadataFormatsRequest();
|
||||
nextRequest.setResumptionToken(token);
|
||||
return nextRequest;
|
||||
}
|
||||
|
||||
public ListSetsRequest resume(ListSetsRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
ListSetsRequest nextRequest = newListSetsRequest();
|
||||
nextRequest.setResumptionToken(token);
|
||||
return nextRequest;
|
||||
}
|
||||
|
||||
public GetRecordRequest resume(GetRecordRequest request, ResumptionToken<?> token) {
|
||||
if (request.isRetry()) {
|
||||
request.setRetry(false);
|
||||
return request;
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
GetRecordRequest nextRequest = newGetRecordRequest();
|
||||
nextRequest.setResumptionToken(token);
|
||||
return nextRequest;
|
||||
return token != null ? nextRequest : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,9 @@ package org.xbib.oai.client.getrecord;
|
|||
|
||||
import org.xbib.oai.client.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class GetRecordRequest extends AbstractOAIRequest {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public GetRecordRequest() {
|
||||
super();
|
||||
addParameter(VERB_PARAMETER, GET_RECORD);
|
||||
|
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.client.getrecord;
|
|||
|
||||
import org.xbib.oai.OAIResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class GetRecordResponse implements OAIResponse {
|
||||
|
||||
public GetRecordResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI get record verb.
|
||||
*/
|
||||
package org.xbib.oai.client.getrecord;
|
|
@ -2,11 +2,9 @@ package org.xbib.oai.client.identify;
|
|||
|
||||
import org.xbib.oai.client.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IdentifyRequest extends AbstractOAIRequest {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public IdentifyRequest() {
|
||||
super();
|
||||
addParameter(VERB_PARAMETER, IDENTIFY);
|
||||
|
|
|
@ -19,9 +19,6 @@ import javax.xml.parsers.DocumentBuilder;
|
|||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IdentifyResponse implements OAIResponse {
|
||||
|
||||
private String repositoryName;
|
||||
|
@ -40,6 +37,9 @@ public class IdentifyResponse implements OAIResponse {
|
|||
|
||||
private String compression;
|
||||
|
||||
public IdentifyResponse() {
|
||||
}
|
||||
|
||||
public void receivedResponse(String message, int statusCode, String contentType, String retryAfter, Writer writer) {
|
||||
try {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI identify verb.
|
||||
*/
|
||||
package org.xbib.oai.client.identify;
|
|
@ -2,11 +2,9 @@ package org.xbib.oai.client.listidentifiers;
|
|||
|
||||
import org.xbib.oai.client.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListIdentifiersRequest extends AbstractOAIRequest {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public ListIdentifiersRequest() {
|
||||
super();
|
||||
addParameter(VERB_PARAMETER, LIST_IDENTIFIERS);
|
||||
|
|
|
@ -4,4 +4,6 @@ import org.xbib.oai.OAIResponse;
|
|||
|
||||
public class ListIdentifiersResponse implements OAIResponse {
|
||||
|
||||
public ListIdentifiersResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list identifiers verb.
|
||||
*/
|
||||
package org.xbib.oai.client.listidentifiers;
|
|
@ -2,11 +2,9 @@ package org.xbib.oai.client.listmetadataformats;
|
|||
|
||||
import org.xbib.oai.client.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListMetadataFormatsRequest extends AbstractOAIRequest {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public ListMetadataFormatsRequest() {
|
||||
super();
|
||||
addParameter(VERB_PARAMETER, LIST_METADATA_FORMATS);
|
||||
|
|
|
@ -4,4 +4,6 @@ import org.xbib.oai.OAIResponse;
|
|||
|
||||
public class ListMetadataFormatsResponse implements OAIResponse {
|
||||
|
||||
public ListMetadataFormatsResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list metadata formats verb.
|
||||
*/
|
||||
package org.xbib.oai.client.listmetadataformats;
|
|
@ -7,17 +7,16 @@ import org.xbib.oai.xml.MetadataHandler;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListRecordsRequest extends AbstractOAIRequest {
|
||||
|
||||
private List<MetadataHandler> handlers = new LinkedList<>();
|
||||
private final List<MetadataHandler> handlers = new LinkedList<>();
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public ListRecordsRequest() {
|
||||
super();
|
||||
addParameter(OAIConstants.VERB_PARAMETER, LIST_RECORDS);
|
||||
}
|
||||
|
||||
public ListRecordsRequest addHandler(MetadataHandler handler) {
|
||||
handlers.add(handler);
|
||||
return this;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list records verb.
|
||||
*/
|
||||
package org.xbib.oai.client.listrecords;
|
|
@ -2,11 +2,9 @@ package org.xbib.oai.client.listsets;
|
|||
|
||||
import org.xbib.oai.client.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListSetsRequest extends AbstractOAIRequest {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public ListSetsRequest() {
|
||||
super();
|
||||
addParameter(VERB_PARAMETER, LIST_SETS);
|
||||
|
|
|
@ -3,4 +3,7 @@ package org.xbib.oai.client.listsets;
|
|||
import org.xbib.oai.OAIResponse;
|
||||
|
||||
public class ListSetsResponse implements OAIResponse {
|
||||
|
||||
public ListSetsResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list sets verb.
|
||||
*/
|
||||
package org.xbib.oai.client.listsets;
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Classes for OAI client.
|
||||
*/
|
||||
package org.xbib.oai.client;
|
196
oai-client/src/main/java/org/xbib/oai/client/util/PercentDecoder.java
Executable file
196
oai-client/src/main/java/org/xbib/oai/client/util/PercentDecoder.java
Executable file
|
@ -0,0 +1,196 @@
|
|||
package org.xbib.oai.client.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
|
||||
import static java.nio.charset.CoderResult.OVERFLOW;
|
||||
import static java.nio.charset.CoderResult.UNDERFLOW;
|
||||
|
||||
/**
|
||||
* Decodes percent-encoded (%XX) Unicode text.
|
||||
*/
|
||||
public final class PercentDecoder {
|
||||
|
||||
/**
|
||||
* bytes represented by the current sequence of %-triples. Resized as needed.
|
||||
*/
|
||||
private ByteBuffer encodedBuf;
|
||||
|
||||
/**
|
||||
* Written to with decoded chars by decoder
|
||||
*/
|
||||
private final CharBuffer decodedCharBuf;
|
||||
private final CharsetDecoder decoder;
|
||||
|
||||
/**
|
||||
* The decoded string for the current input
|
||||
*/
|
||||
private final StringBuilder outputBuf = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Construct a new PercentDecoder with default buffer sizes.
|
||||
*
|
||||
* @param charsetDecoder Charset to decode bytes into chars with
|
||||
* @see PercentDecoder#PercentDecoder(CharsetDecoder, int, int)
|
||||
*/
|
||||
public PercentDecoder(CharsetDecoder charsetDecoder) {
|
||||
this(charsetDecoder, 16, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param charsetDecoder Charset to decode bytes into chars with
|
||||
* @param initialEncodedByteBufSize Initial size of buffer that holds encoded bytes
|
||||
* @param decodedCharBufSize Size of buffer that encoded bytes are decoded into
|
||||
*/
|
||||
public PercentDecoder(CharsetDecoder charsetDecoder, int initialEncodedByteBufSize,
|
||||
int decodedCharBufSize) {
|
||||
encodedBuf = ByteBuffer.allocate(initialEncodedByteBufSize);
|
||||
decodedCharBuf = CharBuffer.allocate(decodedCharBufSize);
|
||||
decoder = charsetDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param input Input with %-encoded representation of characters in this instance's configured character set, e.g.
|
||||
* "%20" for a space character
|
||||
* @return Corresponding string with %-encoded data decoded and converted to their corresponding characters
|
||||
* @throws MalformedInputException if decoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if decoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
public String decode(CharSequence input) throws MalformedInputException, UnmappableCharacterException {
|
||||
outputBuf.setLength(0);
|
||||
// this is almost always an underestimate of the size needed:
|
||||
// only a 4-byte encoding (which is 12 characters input) would case this to be an overestimate
|
||||
outputBuf.ensureCapacity(input.length() / 8);
|
||||
encodedBuf.clear();
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
if (c != '%') {
|
||||
handleEncodedBytes();
|
||||
|
||||
outputBuf.append(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 2 >= input.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not percent decode <" + input + ">: incomplete %-pair at position " + i);
|
||||
}
|
||||
|
||||
// grow the byte buf if needed
|
||||
if (encodedBuf.remaining() == 0) {
|
||||
ByteBuffer largerBuf = ByteBuffer.allocate(encodedBuf.capacity() * 2);
|
||||
encodedBuf.flip();
|
||||
largerBuf.put(encodedBuf);
|
||||
encodedBuf = largerBuf;
|
||||
}
|
||||
|
||||
// note that we advance i here as we consume chars
|
||||
int msBits = Character.digit(input.charAt(++i), 16);
|
||||
int lsBits = Character.digit(input.charAt(++i), 16);
|
||||
|
||||
if (msBits == -1 || lsBits == -1) {
|
||||
throw new IllegalArgumentException("Invalid %-tuple <" + input.subSequence(i - 2, i + 1) + ">");
|
||||
}
|
||||
|
||||
msBits <<= 4;
|
||||
msBits |= lsBits;
|
||||
|
||||
// msBits can only have 8 bits set, so cast is safe
|
||||
encodedBuf.put((byte) msBits);
|
||||
}
|
||||
|
||||
handleEncodedBytes();
|
||||
|
||||
return outputBuf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode any buffered encoded bytes and write them to the output buf.
|
||||
*/
|
||||
private void handleEncodedBytes() throws MalformedInputException, UnmappableCharacterException {
|
||||
if (encodedBuf.position() == 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
decoder.reset();
|
||||
CoderResult coderResult;
|
||||
|
||||
// switch to reading mode
|
||||
encodedBuf.flip();
|
||||
|
||||
// loop while we're filling up the decoded char buf, or there's any encoded bytes
|
||||
// decode() in practice seems to only consume bytes when it can decode an entire char...
|
||||
do {
|
||||
decodedCharBuf.clear();
|
||||
coderResult = decoder.decode(encodedBuf, decodedCharBuf, false);
|
||||
throwIfError(coderResult);
|
||||
appendDecodedChars();
|
||||
} while (coderResult == OVERFLOW && encodedBuf.hasRemaining());
|
||||
|
||||
// final decode with end-of-input flag
|
||||
decodedCharBuf.clear();
|
||||
coderResult = decoder.decode(encodedBuf, decodedCharBuf, true);
|
||||
throwIfError(coderResult);
|
||||
|
||||
if (encodedBuf.hasRemaining()) {
|
||||
throw new IllegalStateException("Final decode didn't error, but didn't consume remaining input bytes");
|
||||
}
|
||||
if (coderResult != UNDERFLOW) {
|
||||
throw new IllegalStateException("Expected underflow, but instead final decode returned " + coderResult);
|
||||
}
|
||||
|
||||
appendDecodedChars();
|
||||
|
||||
// we've finished the input, wrap it up
|
||||
encodedBuf.clear();
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Must only be called when the input encoded bytes buffer is empty
|
||||
*/
|
||||
private void flush() throws MalformedInputException, UnmappableCharacterException {
|
||||
CoderResult coderResult;
|
||||
decodedCharBuf.clear();
|
||||
|
||||
coderResult = decoder.flush(decodedCharBuf);
|
||||
appendDecodedChars();
|
||||
|
||||
throwIfError(coderResult);
|
||||
|
||||
if (coderResult != UNDERFLOW) {
|
||||
throw new IllegalStateException("Decoder flush resulted in " + coderResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If coderResult is considered an error (i.e. not overflow or underflow), throw the corresponding
|
||||
* CharacterCodingException.
|
||||
*
|
||||
* @param coderResult result to check
|
||||
* @throws MalformedInputException if result represents malformed input
|
||||
* @throws UnmappableCharacterException if result represents an unmappable character
|
||||
*/
|
||||
private void throwIfError(CoderResult coderResult) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (coderResult.isMalformed()) {
|
||||
throw new MalformedInputException(coderResult.length());
|
||||
}
|
||||
if (coderResult.isUnmappable()) {
|
||||
throw new UnmappableCharacterException(coderResult.length());
|
||||
} }
|
||||
|
||||
/**
|
||||
* Flip the decoded char buf and append it to the string bug
|
||||
*/
|
||||
private void appendDecodedChars() {
|
||||
decodedCharBuf.flip();
|
||||
outputBuf.append(decodedCharBuf);
|
||||
}
|
||||
}
|
187
oai-client/src/main/java/org/xbib/oai/client/util/PercentEncoder.java
Executable file
187
oai-client/src/main/java/org/xbib/oai/client/util/PercentEncoder.java
Executable file
|
@ -0,0 +1,187 @@
|
|||
package org.xbib.oai.client.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import static java.lang.Character.isHighSurrogate;
|
||||
import static java.lang.Character.isLowSurrogate;
|
||||
|
||||
/**
|
||||
* Encodes unsafe characters as a sequence of %XX hex-encoded bytes.
|
||||
*
|
||||
* This is typically done when encoding components of URLs. See {@link UrlPercentEncoders} for pre-configured
|
||||
* PercentEncoder instances.
|
||||
*/
|
||||
public final class PercentEncoder {
|
||||
|
||||
private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
private final BitSet safeChars;
|
||||
private final CharsetEncoder encoder;
|
||||
/**
|
||||
* Pre-allocate a string handler to make the common case of encoding to a string faster
|
||||
*/
|
||||
private final StringBuilderPercentEncoderOutputHandler stringHandler = new StringBuilderPercentEncoderOutputHandler();
|
||||
private final ByteBuffer encodedBytes;
|
||||
private final CharBuffer unsafeCharsToEncode;
|
||||
|
||||
/**
|
||||
* @param safeChars the set of chars to NOT encode, stored as a bitset with the int positions corresponding to
|
||||
* those chars set to true. Treated as read only.
|
||||
* @param charsetEncoder charset encoder to encode characters with. Make sure to not re-use CharsetEncoder instances
|
||||
* across threads.
|
||||
*/
|
||||
public PercentEncoder(BitSet safeChars, CharsetEncoder charsetEncoder) {
|
||||
this.safeChars = safeChars;
|
||||
this.encoder = charsetEncoder;
|
||||
|
||||
// why is this a float? sigh.
|
||||
int maxBytesPerChar = 1 + (int) encoder.maxBytesPerChar();
|
||||
// need to handle surrogate pairs, so need to be able to handle 2 chars worth of stuff at once
|
||||
encodedBytes = ByteBuffer.allocate(maxBytesPerChar * 2);
|
||||
unsafeCharsToEncode = CharBuffer.allocate(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the input and pass output chars to a handler.
|
||||
*
|
||||
* @param input input string
|
||||
* @param handler handler to call on each output character
|
||||
* @throws MalformedInputException if encoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
public void encode(CharSequence input, StringBuilderPercentEncoderOutputHandler handler) throws
|
||||
MalformedInputException, UnmappableCharacterException {
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
|
||||
char c = input.charAt(i);
|
||||
|
||||
if (safeChars.get(c)) {
|
||||
handler.onOutputChar(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// not a safe char
|
||||
unsafeCharsToEncode.clear();
|
||||
unsafeCharsToEncode.append(c);
|
||||
if (isHighSurrogate(c)) {
|
||||
if (input.length() > i + 1) {
|
||||
// get the low surrogate as well
|
||||
char lowSurrogate = input.charAt(i + 1);
|
||||
if (isLowSurrogate(lowSurrogate)) {
|
||||
unsafeCharsToEncode.append(lowSurrogate);
|
||||
i++;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid UTF-16: Char " + (i) + " is a high surrogate (\\u" + Integer
|
||||
.toHexString(c) + "), but char " + (i + 1) + " is not a low surrogate (\\u" + Integer
|
||||
.toHexString(lowSurrogate) + ")");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid UTF-16: The last character in the input string was a high surrogate (\\u" + Integer
|
||||
.toHexString(c) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
flushUnsafeCharBuffer(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the input and return the resulting text as a String.
|
||||
*
|
||||
* @param input input string
|
||||
* @return the input string with every character that's not in safeChars turned into its byte representation via the
|
||||
* instance's encoder and then percent-encoded
|
||||
* @throws MalformedInputException if encoder is configured to report errors and malformed input is detected
|
||||
* @throws UnmappableCharacterException if encoder is configured to report errors and an unmappable character is
|
||||
* detected
|
||||
*/
|
||||
public String encode(CharSequence input) throws MalformedInputException, UnmappableCharacterException {
|
||||
stringHandler.reset();
|
||||
stringHandler.ensureCapacity(input.length());
|
||||
encode(input, stringHandler);
|
||||
return stringHandler.getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode unsafeCharsToEncode to bytes as per charsetEncoder, then percent-encode those bytes into output.
|
||||
*
|
||||
* Side effects: unsafeCharsToEncode will be read from and cleared. encodedBytes will be cleared and written to.
|
||||
*
|
||||
*/
|
||||
private void flushUnsafeCharBuffer(StringBuilderPercentEncoderOutputHandler handler) throws MalformedInputException,
|
||||
UnmappableCharacterException {
|
||||
// need to read from the char buffer, which was most recently written to
|
||||
unsafeCharsToEncode.flip();
|
||||
|
||||
encodedBytes.clear();
|
||||
|
||||
encoder.reset();
|
||||
CoderResult result = encoder.encode(unsafeCharsToEncode, encodedBytes, true);
|
||||
checkResult(result);
|
||||
result = encoder.flush(encodedBytes);
|
||||
checkResult(result);
|
||||
|
||||
// read contents of bytebuffer
|
||||
encodedBytes.flip();
|
||||
|
||||
while (encodedBytes.hasRemaining()) {
|
||||
byte b = encodedBytes.get();
|
||||
handler.onOutputChar('%');
|
||||
handler.onOutputChar(HEX_CODE[b >> 4 & 0xF]);
|
||||
handler.onOutputChar(HEX_CODE[b & 0xF]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result result to check
|
||||
* @throws IllegalStateException if result is overflow
|
||||
* @throws MalformedInputException if result represents malformed input
|
||||
* @throws UnmappableCharacterException if result represents an unmappable character
|
||||
*/
|
||||
private static void checkResult(CoderResult result) throws MalformedInputException, UnmappableCharacterException {
|
||||
if (result.isOverflow()) {
|
||||
throw new IllegalStateException("Byte buffer overflow; this should not happen.");
|
||||
}
|
||||
if (result.isMalformed()) {
|
||||
throw new MalformedInputException(result.length());
|
||||
}
|
||||
if (result.isUnmappable()) {
|
||||
throw new UnmappableCharacterException(result.length());
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringBuilderPercentEncoderOutputHandler {
|
||||
|
||||
private final StringBuilder stringBuilder;
|
||||
|
||||
public StringBuilderPercentEncoderOutputHandler() {
|
||||
stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
public String getContents() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
stringBuilder.setLength(0);
|
||||
}
|
||||
|
||||
public void ensureCapacity(int length) {
|
||||
stringBuilder.ensureCapacity(length);
|
||||
}
|
||||
|
||||
public void onOutputChar(char c) {
|
||||
stringBuilder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
504
oai-client/src/main/java/org/xbib/oai/client/util/UrlBuilder.java
Executable file
504
oai-client/src/main/java/org/xbib/oai/client/util/UrlBuilder.java
Executable file
|
@ -0,0 +1,504 @@
|
|||
package org.xbib.oai.client.util;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getFragmentEncoder;
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getMatrixEncoder;
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getPathEncoder;
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getQueryParamEncoder;
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getRegNameEncoder;
|
||||
import static org.xbib.oai.client.util.UrlPercentEncoders.getUnstructuredQueryEncoder;
|
||||
|
||||
/**
|
||||
* Builder for urls with url-encoding applied to path, query param, etc.
|
||||
*
|
||||
* Escaping rules are from RFC 3986, RFC 1738 and the <a href ="http://www.w3.org/TR/html401/interact/forms.html#form-content-type">HTML 4 spec</a>
|
||||
* This means that this diverges from the canonical URI/URL rules for the sake of being what you want to actually make
|
||||
* HTTP-useful URLs.
|
||||
*/
|
||||
public final class UrlBuilder {
|
||||
|
||||
/**
|
||||
* IPv6 address, cribbed from <a href="http://stackoverflow.com/questions/46146/what-are-the-java-regular-expressions-for-matching-ipv4-and-ipv6-strings">StackOverflow</a>
|
||||
*/
|
||||
private static final Pattern IPV6_PATTERN = Pattern
|
||||
.compile(
|
||||
"\\A\\[((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)]\\z");
|
||||
|
||||
/**
|
||||
* IPv4 dotted quad
|
||||
*/
|
||||
private static final Pattern IPV4_PATTERN = Pattern
|
||||
.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
|
||||
|
||||
private final String scheme;
|
||||
|
||||
private final String host;
|
||||
|
||||
private final Integer port;
|
||||
|
||||
private final List<Pair<String, String>> queryParams = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* If this is non-null, queryParams must be empty, and vice versa.
|
||||
*/
|
||||
private String unstructuredQuery;
|
||||
|
||||
private final List<PathSegment> pathSegments = new ArrayList<>();
|
||||
|
||||
private final PercentEncoder pathEncoder = getPathEncoder();
|
||||
private final PercentEncoder regNameEncoder = getRegNameEncoder();
|
||||
private final PercentEncoder matrixEncoder = getMatrixEncoder();
|
||||
private final PercentEncoder queryParamEncoder = getQueryParamEncoder();
|
||||
private final PercentEncoder unstructuredQueryEncoder = getUnstructuredQueryEncoder();
|
||||
private final PercentEncoder fragmentEncoder = getFragmentEncoder();
|
||||
|
||||
private String fragment;
|
||||
|
||||
private boolean forceTrailingSlash = false;
|
||||
|
||||
/**
|
||||
* Create a URL with UTF-8 encoding.
|
||||
*
|
||||
* @param scheme scheme (e.g. http)
|
||||
* @param host host (e.g. foo.com or 1.2.3.4 or [::1])
|
||||
* @param port null or a positive integer
|
||||
*/
|
||||
private UrlBuilder(String scheme, String host, Integer port) {
|
||||
this.host = host;
|
||||
this.scheme = scheme;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a URL with an null port and UTF-8 encoding.
|
||||
*
|
||||
* @param scheme scheme (e.g. http)
|
||||
* @param host host in any of the valid syntaxes: reg-name (a dns name), ipv4 literal (1.2.3.4), ipv6 literal
|
||||
* ([::1]), excluding IPvFuture since no one uses that in practice
|
||||
* @return a url builder
|
||||
* @see UrlBuilder#forHost(String scheme, String host, int port)
|
||||
*/
|
||||
public static UrlBuilder forHost(String scheme, String host) {
|
||||
return new UrlBuilder(scheme, host, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scheme scheme (e.g. http)
|
||||
* @param host host in any of the valid syntaxes: reg-name ( a dns name), ipv4 literal (1.2.3.4), ipv6 literal
|
||||
* ([::1]), excluding IPvFuture since no one uses that in practice
|
||||
* @param port port
|
||||
* @return a url builder
|
||||
*/
|
||||
public static UrlBuilder forHost(String scheme, String host, int port) {
|
||||
return new UrlBuilder(scheme, host, port);
|
||||
}
|
||||
|
||||
public static UrlBuilder fromUrl(String urlSpec) throws CharacterCodingException, MalformedURLException {
|
||||
return fromUrl(URI.create(urlSpec).toURL());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link UrlBuilder#fromUrl(URL, CharsetDecoder)} with a UTF-8 CharsetDecoder. The same semantics about the
|
||||
* query string apply.
|
||||
*
|
||||
* @param url url to initialize builder with
|
||||
* @return a UrlBuilder containing the host, path, etc. from the url
|
||||
* @throws CharacterCodingException if char decoding fails
|
||||
* @see UrlBuilder#fromUrl(URL, CharsetDecoder)
|
||||
*/
|
||||
public static UrlBuilder fromUrl(URL url) throws CharacterCodingException {
|
||||
return fromUrl(url, StandardCharsets.UTF_8.newDecoder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UrlBuilder initialized with the contents of a {@link URL}.
|
||||
*
|
||||
* The query string will be parsed into HTML4 query params if it can be separated into a
|
||||
* <code>&</code>-separated sequence of <code>key=value</code> pairs. The sequence of query params can then be
|
||||
* appended to by continuing to call {@link UrlBuilder#queryParam(String, String)}. The concept of query params is
|
||||
* only part of the HTML spec (and common HTTP usage), though, so it's perfectly legal to have a query string that
|
||||
* is in some other form. To represent this case, if the aforementioned param-parsing attempt fails, the query
|
||||
* string will be treated as just a monolithic, unstructured, string. In this case, calls to {@link
|
||||
* UrlBuilder#queryParam(String, String)} on the resulting instance will throw IllegalStateException, and only calls
|
||||
* to {@link UrlBuilder#unstructuredQuery(String)}}, which replaces the entire query string, are allowed.
|
||||
*
|
||||
* @param url url to initialize builder with
|
||||
* @param charsetDecoder the decoder to decode encoded bytes with (except for reg names, which are always UTF-8)
|
||||
* @return a UrlBuilder containing the host, path, etc. from the url
|
||||
* @throws CharacterCodingException if decoding percent-encoded bytes fails and charsetDecoder is configured to
|
||||
* report errors
|
||||
* @see UrlBuilder#fromUrl(URL, CharsetDecoder)
|
||||
*/
|
||||
public static UrlBuilder fromUrl(URL url, CharsetDecoder charsetDecoder) throws
|
||||
CharacterCodingException {
|
||||
|
||||
PercentDecoder decoder = new PercentDecoder(charsetDecoder);
|
||||
// reg names must be encoded UTF-8
|
||||
PercentDecoder regNameDecoder;
|
||||
if (charsetDecoder.charset().equals(StandardCharsets.UTF_8)) {
|
||||
regNameDecoder = decoder;
|
||||
} else {
|
||||
regNameDecoder = new PercentDecoder(StandardCharsets.UTF_8.newDecoder());
|
||||
}
|
||||
|
||||
Integer port = url.getPort();
|
||||
if (port == -1) {
|
||||
port = null;
|
||||
}
|
||||
|
||||
UrlBuilder builder = new UrlBuilder(url.getProtocol(), regNameDecoder.decode(url.getHost()), port);
|
||||
|
||||
buildFromPath(builder, decoder, url);
|
||||
|
||||
buildFromQuery(builder, decoder, url);
|
||||
|
||||
if (url.getRef() != null) {
|
||||
builder.fragment(decoder.decode(url.getRef()));
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path segment.
|
||||
*
|
||||
* @param segment a path segment
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder pathSegment(String segment) {
|
||||
pathSegments.add(new PathSegment(segment));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple path segments. Equivalent to successive calls to {@link UrlBuilder#pathSegment(String)}.
|
||||
*
|
||||
* @param segments path segments
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder pathSegments(String... segments) {
|
||||
for (String segment : segments) {
|
||||
pathSegment(segment);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an HTML query parameter. Query parameters will be encoded in the order added.
|
||||
*
|
||||
* Using query strings to encode key=value pairs is not part of the URI/URL specification; it is specified by
|
||||
* <a href="http://www.w3.org/TR/html401/interact/forms.html#form-content-type">HTML 4</a>.
|
||||
*
|
||||
* If you use this method to build a query string, or created this builder from a url with a query string that can
|
||||
* successfully be parsed into query param pairs, you cannot subsequently use {@link
|
||||
* UrlBuilder#unstructuredQuery(String)}. See {@link UrlBuilder#fromUrl(URL, CharsetDecoder)}.
|
||||
*
|
||||
* @param name param name
|
||||
* @param value param value
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder queryParam(String name, String value) {
|
||||
if (unstructuredQuery != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot call queryParam() when this already has an unstructured query specified");
|
||||
}
|
||||
|
||||
queryParams.add(Pair.of(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the complete query string of arbitrary structure. This is useful when you want to specify a query string that
|
||||
* is not of key=value format. If the query has previously been set via this method, subsequent calls will overwrite
|
||||
* that query.
|
||||
*
|
||||
* If you use this method, or create a builder from a URL whose query is not parseable into query param pairs, you
|
||||
* cannot subsequently use {@link UrlBuilder#queryParam(String, String)}. See {@link UrlBuilder#fromUrl(URL,
|
||||
* CharsetDecoder)}.
|
||||
*
|
||||
* @param query Complete URI query, as specified by <a href="https://tools.ietf.org/html/rfc3986#section-3.4">RFC 3986</a>.
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder unstructuredQuery(String query) {
|
||||
if (!queryParams.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot call unstructuredQuery() when this already has queryParam pairs specified");
|
||||
}
|
||||
|
||||
unstructuredQuery = query;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the unstructured query and any query params.
|
||||
*
|
||||
* Since the query / query param situation is a little complicated, this method will let you remove all query
|
||||
* information and start again from scratch. This may be useful when taking an existing url, parsing it into a
|
||||
* builder, and then re-doing its query params, for instance.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder clearQuery() {
|
||||
queryParams.clear();
|
||||
unstructuredQuery = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a matrix param to the last added path segment. If no segments have been added, the param will be added to the
|
||||
* root. Matrix params will be encoded in the order added.
|
||||
*
|
||||
* @param name param name
|
||||
* @param value param value
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder matrixParam(String name, String value) {
|
||||
if (pathSegments.isEmpty()) {
|
||||
// create an empty path segment to represent a matrix param applied to the root
|
||||
pathSegment("");
|
||||
}
|
||||
|
||||
PathSegment seg = pathSegments.get(pathSegments.size() - 1);
|
||||
seg.matrixParams.add(Pair.of(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fragment.
|
||||
*
|
||||
* @param fragment fragment string
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder fragment(String fragment) {
|
||||
this.fragment = fragment;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the generated URL to have a trailing slash at the end of the path.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public UrlBuilder forceTrailingSlash() {
|
||||
forceTrailingSlash = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public URL build() throws CharacterCodingException, MalformedURLException {
|
||||
return URI.create(toUrlString()).toURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the current builder state into a URL string.
|
||||
*
|
||||
* @return a well-formed URL string
|
||||
* @throws CharacterCodingException if character encoding fails and the encoder is configured to report errors
|
||||
*/
|
||||
public String toUrlString() throws CharacterCodingException {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
buf.append(scheme);
|
||||
buf.append("://");
|
||||
|
||||
buf.append(encodeHost(host));
|
||||
if (port != null) {
|
||||
buf.append(':');
|
||||
buf.append(port);
|
||||
}
|
||||
|
||||
for (PathSegment pathSegment : pathSegments) {
|
||||
buf.append('/');
|
||||
buf.append(pathEncoder.encode(pathSegment.segment));
|
||||
|
||||
for (Pair<String, String> matrixParam : pathSegment.matrixParams) {
|
||||
buf.append(';');
|
||||
buf.append(matrixEncoder.encode(matrixParam.getKey()));
|
||||
buf.append('=');
|
||||
buf.append(matrixEncoder.encode(matrixParam.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
if (forceTrailingSlash) {
|
||||
buf.append('/');
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
buf.append("?");
|
||||
Iterator<Pair<String, String>> qpIter = queryParams.iterator();
|
||||
while (qpIter.hasNext()) {
|
||||
Pair<String, String> queryParam = qpIter.next();
|
||||
buf.append(queryParamEncoder.encode(queryParam.getKey()));
|
||||
buf.append('=');
|
||||
buf.append(queryParamEncoder.encode(queryParam.getValue()));
|
||||
if (qpIter.hasNext()) {
|
||||
buf.append('&');
|
||||
}
|
||||
}
|
||||
} else if (unstructuredQuery != null) {
|
||||
buf.append("?");
|
||||
buf.append(unstructuredQueryEncoder.encode(unstructuredQuery));
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
buf.append('#');
|
||||
buf.append(fragmentEncoder.encode(fragment));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a url builder based on the query of a url
|
||||
*
|
||||
* @param builder builder
|
||||
* @param decoder decoder
|
||||
* @param url url
|
||||
* @throws CharacterCodingException
|
||||
*/
|
||||
private static void buildFromQuery(UrlBuilder builder, PercentDecoder decoder, URL url) throws
|
||||
CharacterCodingException {
|
||||
if (url.getQuery() != null) {
|
||||
String q = url.getQuery();
|
||||
|
||||
// try to parse into &-separated key=value pairs
|
||||
List<Pair<String, String>> pairs = new ArrayList<>();
|
||||
boolean parseOk = true;
|
||||
|
||||
for (String queryChunk : q.split("&")) {
|
||||
String[] queryParamChunks = queryChunk.split("=");
|
||||
|
||||
if (queryParamChunks.length != 2) {
|
||||
parseOk = false;
|
||||
break;
|
||||
}
|
||||
|
||||
pairs.add(Pair.of(decoder.decode(queryParamChunks[0]),
|
||||
decoder.decode(queryParamChunks[1])));
|
||||
}
|
||||
|
||||
if (parseOk) {
|
||||
for (Pair<String, String> pair : pairs) {
|
||||
builder.queryParam(pair.getKey(), pair.getValue());
|
||||
}
|
||||
} else {
|
||||
builder.unstructuredQuery(decoder.decode(q));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the path segments of a url builder from a url
|
||||
*
|
||||
* @param builder builder
|
||||
* @param decoder decoder
|
||||
* @param url url
|
||||
* @throws CharacterCodingException
|
||||
*/
|
||||
private static void buildFromPath(UrlBuilder builder, PercentDecoder decoder, URL url) throws
|
||||
CharacterCodingException {
|
||||
for (String pathChunk : url.getPath().split("/")) {
|
||||
if (pathChunk.equals("")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pathChunk.charAt(0) == ';') {
|
||||
builder.pathSegment("");
|
||||
// empty path segment, but matrix params
|
||||
for (String matrixChunk : pathChunk.substring(1).split(";")) {
|
||||
buildFromMatrixParamChunk(decoder, builder, matrixChunk);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise, path chunk is non empty and does not start with a ';'
|
||||
|
||||
String[] matrixChunks = pathChunk.split(";");
|
||||
|
||||
// first chunk is always the path segment. If there is a trailing ; and no matrix params, the ; will
|
||||
// not be included in the final url.
|
||||
builder.pathSegment(decoder.decode(matrixChunks[0]));
|
||||
|
||||
// if there any other chunks, they're matrix param pairs
|
||||
for (int i = 1; i < matrixChunks.length; i++) {
|
||||
buildFromMatrixParamChunk(decoder, builder, matrixChunks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildFromMatrixParamChunk(PercentDecoder decoder, UrlBuilder ub, String pathMatrixChunk) throws
|
||||
CharacterCodingException {
|
||||
String[] mtxPair = pathMatrixChunk.split("=");
|
||||
if (mtxPair.length != 2) {
|
||||
throw new IllegalArgumentException("Malformed matrix param: <" + pathMatrixChunk + ">");
|
||||
}
|
||||
|
||||
String mtxName = mtxPair[0];
|
||||
String mtxVal = mtxPair[1];
|
||||
ub.matrixParam(decoder.decode(mtxName), decoder.decode(mtxVal));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host original host string
|
||||
* @return host encoded as in RFC 3986 section 3.2.2
|
||||
*/
|
||||
private String encodeHost(String host) throws CharacterCodingException {
|
||||
// matching order: IP-literal, IPv4, reg-name
|
||||
if (IPV4_PATTERN.matcher(host).matches() || IPV6_PATTERN.matcher(host).matches()) {
|
||||
return host;
|
||||
}
|
||||
|
||||
// it's a reg-name, which MUST be encoded as UTF-8 (regardless of the rest of the URL)
|
||||
return regNameEncoder.encode(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle of a path segment name and any associated matrix params.
|
||||
*/
|
||||
private static class PathSegment {
|
||||
private final String segment;
|
||||
private final List<Pair<String, String>> matrixParams = new ArrayList<>();
|
||||
|
||||
PathSegment(String segment) {
|
||||
this.segment = segment;
|
||||
}
|
||||
}
|
||||
|
||||
static class Pair<K,V> {
|
||||
|
||||
K key;
|
||||
|
||||
V value;
|
||||
|
||||
Pair(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static <K,V> Pair<K,V> of(K key, V value) {
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
|
||||
K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
164
oai-client/src/main/java/org/xbib/oai/client/util/UrlPercentEncoders.java
Executable file
164
oai-client/src/main/java/org/xbib/oai/client/util/UrlPercentEncoders.java
Executable file
|
@ -0,0 +1,164 @@
|
|||
package org.xbib.oai.client.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.BitSet;
|
||||
import static java.nio.charset.CodingErrorAction.REPLACE;
|
||||
|
||||
/**
|
||||
* See RFC 3986, RFC 1738 and http://www.lunatech-research.com/archives/2009/02/03/what-every-web-developer-must-know-about-url-encoding.
|
||||
*/
|
||||
public final class UrlPercentEncoders {
|
||||
|
||||
/**
|
||||
* an encoder for RFC 3986 reg-names
|
||||
*/
|
||||
|
||||
private static final BitSet REG_NAME_BIT_SET = new BitSet();
|
||||
|
||||
private static final BitSet PATH_BIT_SET = new BitSet();
|
||||
private static final BitSet MATRIX_BIT_SET = new BitSet();
|
||||
private static final BitSet UNSTRUCTURED_QUERY_BIT_SET = new BitSet();
|
||||
private static final BitSet QUERY_PARAM_BIT_SET = new BitSet();
|
||||
private static final BitSet FRAGMENT_BIT_SET = new BitSet();
|
||||
|
||||
static {
|
||||
// RFC 3986 'reg-name'. This is not very aggressive... it's quite possible to have DNS-illegal names out of this.
|
||||
// Regardless, it will at least be URI-compliant even if it's not HTTP URL-compliant.
|
||||
addUnreserved(REG_NAME_BIT_SET);
|
||||
addSubdelims(REG_NAME_BIT_SET);
|
||||
|
||||
// Represents RFC 3986 'pchar'. Remove delimiter that starts matrix section.
|
||||
addPChar(PATH_BIT_SET);
|
||||
PATH_BIT_SET.clear((int) ';');
|
||||
|
||||
// Remove delims for HTTP matrix params as per RFC 1738 S3.3. The other reserved chars ('/' and '?') are already excluded.
|
||||
addPChar(MATRIX_BIT_SET);
|
||||
MATRIX_BIT_SET.clear((int) ';');
|
||||
MATRIX_BIT_SET.clear((int) '=');
|
||||
|
||||
/*
|
||||
* At this point it represents RFC 3986 'query'. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 also
|
||||
* specifies that "+" can mean space in a query, so we will make sure to say that '+' is not safe to leave as-is
|
||||
*/
|
||||
addQuery(UNSTRUCTURED_QUERY_BIT_SET);
|
||||
UNSTRUCTURED_QUERY_BIT_SET.clear((int) '+');
|
||||
|
||||
/*
|
||||
* Create more stringent requirements for HTML4 queries: remove delimiters for HTML query params so that key=value
|
||||
* pairs can be used.
|
||||
*/
|
||||
QUERY_PARAM_BIT_SET.or(UNSTRUCTURED_QUERY_BIT_SET);
|
||||
QUERY_PARAM_BIT_SET.clear((int) '=');
|
||||
QUERY_PARAM_BIT_SET.clear((int) '&');
|
||||
|
||||
addFragment(FRAGMENT_BIT_SET);
|
||||
}
|
||||
|
||||
public static PercentEncoder getRegNameEncoder() {
|
||||
return new PercentEncoder(REG_NAME_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
public static PercentEncoder getPathEncoder() {
|
||||
return new PercentEncoder(PATH_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
public static PercentEncoder getMatrixEncoder() {
|
||||
return new PercentEncoder(MATRIX_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
public static PercentEncoder getUnstructuredQueryEncoder() {
|
||||
return new PercentEncoder(UNSTRUCTURED_QUERY_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
public static PercentEncoder getQueryParamEncoder() {
|
||||
return new PercentEncoder(QUERY_PARAM_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
public static PercentEncoder getFragmentEncoder() {
|
||||
return new PercentEncoder(FRAGMENT_BIT_SET, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
private UrlPercentEncoders() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'fragment' chars
|
||||
*
|
||||
* @param fragmentBitSet bit set
|
||||
*/
|
||||
private static void addFragment(BitSet fragmentBitSet) {
|
||||
addPChar(fragmentBitSet);
|
||||
fragmentBitSet.set((int) '/');
|
||||
fragmentBitSet.set((int) '?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'query' chars
|
||||
*
|
||||
* @param queryBitSet bit set
|
||||
*/
|
||||
private static void addQuery(BitSet queryBitSet) {
|
||||
addPChar(queryBitSet);
|
||||
queryBitSet.set((int) '/');
|
||||
queryBitSet.set((int) '?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code points for 'pchar' chars.
|
||||
*
|
||||
* @param bs bitset
|
||||
*/
|
||||
private static void addPChar(BitSet bs) {
|
||||
addUnreserved(bs);
|
||||
addSubdelims(bs);
|
||||
bs.set((int) ':');
|
||||
bs.set((int) '@');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add codepoints for 'unreserved' chars
|
||||
*
|
||||
* @param bs bitset to add codepoints to
|
||||
*/
|
||||
private static void addUnreserved(BitSet bs) {
|
||||
|
||||
for (int i = 'a'; i <= 'z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = '0'; i <= '9'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
bs.set((int) '-');
|
||||
bs.set((int) '.');
|
||||
bs.set((int) '_');
|
||||
bs.set((int) '~');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add codepoints for 'sub-delims' chars
|
||||
*
|
||||
* @param bs bitset to add codepoints to
|
||||
*/
|
||||
private static void addSubdelims(BitSet bs) {
|
||||
bs.set((int) '!');
|
||||
bs.set((int) '$');
|
||||
bs.set((int) '&');
|
||||
bs.set((int) '\'');
|
||||
bs.set((int) '(');
|
||||
bs.set((int) ')');
|
||||
bs.set((int) '*');
|
||||
bs.set((int) '+');
|
||||
bs.set((int) ',');
|
||||
bs.set((int) ';');
|
||||
bs.set((int) '=');
|
||||
}
|
||||
}
|
8
oai-client/src/test/java/module-info.java
Normal file
8
oai-client/src/test/java/module-info.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
module org.xbib.oai.client.test {
|
||||
requires java.logging;
|
||||
requires org.junit.jupiter.api;
|
||||
requires org.xbib.marc;
|
||||
requires org.xbib.oai;
|
||||
requires org.xbib.oai.client;
|
||||
opens org.xbib.oai.client.test to org.junit.platform.commons;
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package org.xbib.oai.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.identify.IdentifyResponse;
|
||||
import org.xbib.oai.xml.SimpleMetadataHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class DNBClientTest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DNBClientTest.class.getName());
|
||||
|
||||
@Test
|
||||
void testBibdatFileDump() throws Exception {
|
||||
OAIClient oaiClient = new OAIClient("http://services.dnb.de/oai/repository");
|
||||
Instant from = Instant.parse("2021-02-01T00:00:00Z");
|
||||
Instant until = Instant.parse("2021-03-01T00:00:00Z");
|
||||
Instant base = Instant.parse("2010-01-01T00:00:00Z");
|
||||
SplitWriter splitWriter = new SplitWriter("build/dnb-bib-pica-%d.xml", -1, 8192, false);
|
||||
oaiClient.setSplitWriter(splitWriter);
|
||||
oaiClient.listRecords("PicaPlus-xml", "bib",
|
||||
null, from, until, base,
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBibdatSimpleMetadataHandler() throws Exception {
|
||||
OAIClient oaiClient = new OAIClient("http://services.dnb.de/oai/repository");
|
||||
IdentifyResponse identifyResponse = oaiClient.identify();
|
||||
String granularity = identifyResponse.getGranularity();
|
||||
logger.log(Level.INFO, "granularity = " + granularity);
|
||||
DateTimeFormatter dateTimeFormatter = "YYYY-MM-DD".equals(granularity) ?
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("UTC")) : null;
|
||||
Handler handler = new Handler();
|
||||
oaiClient.listRecords("PicaPlus-xml", "bib",
|
||||
dateTimeFormatter, Instant.parse("2016-01-01T00:00:00Z"), Instant.parse("2016-01-10T00:00:00Z"), null,
|
||||
handler, null);
|
||||
logger.log(Level.INFO, "count=" + handler.count());
|
||||
assertTrue(handler.count() > 0);
|
||||
}
|
||||
|
||||
static class Handler extends SimpleMetadataHandler {
|
||||
|
||||
final AtomicLong count = new AtomicLong(0L);
|
||||
|
||||
@Override
|
||||
public void startDocument() {
|
||||
logger.log(Level.INFO, "start doc");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocument() {
|
||||
logger.log(Level.INFO, "end doc");
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
long count() {
|
||||
return count.get();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Classes for testing OAI client.
|
||||
*/
|
||||
package org.xbib.oai.client;
|
|
@ -1,8 +1,10 @@
|
|||
package org.xbib.oai.client;
|
||||
package org.xbib.oai.client.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.OAIClient;
|
||||
import org.xbib.oai.client.SplitWriter;
|
||||
import org.xbib.oai.client.identify.IdentifyResponse;
|
||||
import org.xbib.oai.xml.SimpleMetadataHandler;
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package org.xbib.oai.client;
|
||||
package org.xbib.oai.client.test;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.marc.Marc;
|
||||
import org.xbib.marc.json.MarcJsonWriter;
|
||||
import org.xbib.marc.xml.MarcContentHandler;
|
||||
import org.xbib.oai.client.OAIClient;
|
||||
import org.xbib.oai.client.identify.IdentifyResponse;
|
||||
import org.xbib.oai.exceptions.OAIException;
|
||||
|
||||
|
@ -15,12 +17,12 @@ import java.util.EnumSet;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Disabled("takes long time")
|
||||
class BundeskunsthalleTest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BundeskunsthalleTest.class.getName());
|
||||
|
||||
@Test
|
||||
// @Disabled("takes long time")
|
||||
void testListRecords() throws Exception {
|
||||
OAIClient oaiClient = new OAIClient("https://www.bundeskunsthalle.de/cgi-bin/bib/oai-pmh");
|
||||
IdentifyResponse identifyResponse = oaiClient.identify();
|
||||
|
@ -28,8 +30,8 @@ class BundeskunsthalleTest {
|
|||
logger.log(Level.INFO, "granularity = " + granularity);
|
||||
DateTimeFormatter dateTimeFormatter = "YYYY-MM-DD".equals(granularity) ?
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("UTC")) : null;
|
||||
try (MarcJsonWriter writer = new MarcJsonWriter("build/bk-bulk%d.jsonl", 1000,
|
||||
EnumSet.of(MarcJsonWriter.Style.ELASTICSEARCH_BULK), 65536, false)
|
||||
try (MarcJsonWriter writer = new MarcJsonWriter("build/bk-bulk%d.jsonl", 1000, 8192, false)
|
||||
.setStyle(EnumSet.of(MarcJsonWriter.Style.ELASTICSEARCH_BULK))
|
||||
.setIndex("bk", "type")) {
|
||||
writer.startDocument();
|
||||
writer.beginCollection();
|
|
@ -0,0 +1,40 @@
|
|||
package org.xbib.oai.client.test;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.OAIClient;
|
||||
import org.xbib.oai.client.SplitWriter;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Disabled("port is locked")
|
||||
class DNBClientTest {
|
||||
|
||||
@Test
|
||||
void testBibdatFileDump() throws Exception {
|
||||
OAIClient oaiClient = new OAIClient("http://services.dnb.de/oai/repository");
|
||||
Instant from = Instant.parse("2021-02-01T00:00:00Z");
|
||||
Instant until = Instant.parse("2021-03-01T00:00:00Z");
|
||||
Instant base = Instant.parse("2010-01-01T00:00:00Z");
|
||||
SplitWriter splitWriter = new SplitWriter("build/dnb-bib-pica-%d.xml", -1, 8192, false);
|
||||
oaiClient.setSplitWriter(splitWriter);
|
||||
oaiClient.listRecords("PicaPlus-xml", "bib",
|
||||
null, from, until, base,
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBibdatSimpleMetadataHandler() throws Exception {
|
||||
OAIClient oaiClient = new OAIClient("http://services.dnb.de/oai/repository");
|
||||
oaiClient.setUserAgent("Hochschulbibliothekszentrum Nordrhein-Westfalen (prante@hbz-nrw.de)");
|
||||
Instant base = Instant.parse("2010-01-01T00:00:00Z");
|
||||
|
||||
SplitWriter splitWriter = new SplitWriter("build/bibdat-test-%d.xml", -1, 8192, false);
|
||||
oaiClient.setSplitWriter(splitWriter);
|
||||
oaiClient.listRecords("PicaPlus-xml", "bib", null,
|
||||
Instant.parse("2016-01-01T00:00:00Z"),
|
||||
Instant.parse("2016-02-01T00:00:00Z"),
|
||||
base, null, null);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package org.xbib.oai.client;
|
||||
package org.xbib.oai.client.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.OAIClient;
|
||||
import org.xbib.oai.client.SplitWriter;
|
||||
import org.xbib.oai.client.identify.IdentifyResponse;
|
||||
import org.xbib.oai.xml.SimpleMetadataHandler;
|
||||
|
||||
|
@ -23,13 +25,15 @@ class DOAJClientTest {
|
|||
IdentifyResponse identifyResponse = oaiClient.identify();
|
||||
String granularity = identifyResponse.getGranularity();
|
||||
logger.log(Level.INFO, "granularity = " + granularity);
|
||||
DateTimeFormatter dateTimeFormatter = "YYYY-MM-DD".equals(granularity) ?
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("GMT")) : null;
|
||||
// override granularity because of "bad arguments" error. Seems DOAJ is unable to manage it's own declared granularity.
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.of("GMT"));
|
||||
Handler handler = new Handler();
|
||||
SplitWriter splitWriter = new SplitWriter("build/doaj-%d.xml", -1, 8192, false);
|
||||
oaiClient.setSplitWriter(splitWriter);
|
||||
Instant to = Instant.now();
|
||||
Instant from = to.atZone(ZoneId.systemDefault()).minusMonths(1).toInstant();
|
||||
oaiClient.listRecords("oai_dc", null,
|
||||
dateTimeFormatter,Instant.parse("2021-01-01T00:00:00Z"), Instant.parse("2021-02-01T00:00:00Z"), null,
|
||||
dateTimeFormatter, from, to, null,
|
||||
handler, null);
|
||||
logger.log(Level.INFO, "count = " + handler.count());
|
||||
assertTrue(handler.count() > 0);
|
85
oai-client/src/test/java/org/xbib/oai/client/test/PercentEncoderTest.java
Executable file
85
oai-client/src/test/java/org/xbib/oai/client/test/PercentEncoderTest.java
Executable file
|
@ -0,0 +1,85 @@
|
|||
package org.xbib.oai.client.test;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.util.PercentEncoder;
|
||||
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import static java.nio.charset.CodingErrorAction.REPLACE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public final class PercentEncoderTest {
|
||||
|
||||
private static PercentEncoder alnum;
|
||||
private static PercentEncoder alnum16;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
BitSet bs = new BitSet();
|
||||
for (int i = 'a'; i <= 'z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
for (int i = '0'; i <= '9'; i++) {
|
||||
bs.set(i);
|
||||
}
|
||||
|
||||
alnum = new PercentEncoder(bs, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
alnum16 = new PercentEncoder(bs, StandardCharsets.UTF_16BE.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesntEncodeSafe() throws CharacterCodingException {
|
||||
BitSet set = new BitSet();
|
||||
for (int i = 'a'; i <= 'z'; i++) {
|
||||
set.set(i);
|
||||
}
|
||||
|
||||
PercentEncoder pe = new PercentEncoder(set, StandardCharsets.UTF_8.newEncoder().onMalformedInput(REPLACE)
|
||||
.onUnmappableCharacter(REPLACE));
|
||||
assertEquals("abcd%41%42%43%44", pe.encode("abcdABCD"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeInBetweenSafe() throws MalformedInputException, UnmappableCharacterException {
|
||||
assertEquals("abc%20123", alnum.encode("abc 123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSafeInBetweenEncoded() throws MalformedInputException, UnmappableCharacterException {
|
||||
assertEquals("%20abc%20", alnum.encode(" abc "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeUtf8() throws CharacterCodingException {
|
||||
// 1 UTF-16 char (unicode snowman)
|
||||
assertEquals("snowman%E2%98%83", alnum.encode("snowman\u2603"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeUtf8SurrogatePair() throws CharacterCodingException {
|
||||
// musical G clef: 1d11e, has to be represented in surrogate pair form
|
||||
assertEquals("clef%F0%9D%84%9E", alnum.encode("clef\ud834\udd1e"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeUtf16() throws CharacterCodingException {
|
||||
// 1 UTF-16 char (unicode snowman)
|
||||
assertEquals("snowman%26%03", alnum16.encode("snowman\u2603"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUrlEncodedUtf16SurrogatePair() throws CharacterCodingException {
|
||||
// musical G clef: 1d11e, has to be represented in surrogate pair form
|
||||
assertEquals("clef%D8%34%DD%1E", alnum16.encode("clef\ud834\udd1e"));
|
||||
}
|
||||
}
|
428
oai-client/src/test/java/org/xbib/oai/client/test/UrlBuilderTest.java
Executable file
428
oai-client/src/test/java/org/xbib/oai/client/test/UrlBuilderTest.java
Executable file
|
@ -0,0 +1,428 @@
|
|||
package org.xbib.oai.client.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.oai.client.util.UrlBuilder;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class UrlBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testNoUrlParts() throws Exception {
|
||||
assertUrlEquals("http://foo.com", UrlBuilder.forHost("http", "foo.com").toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPort() throws Exception {
|
||||
assertUrlEquals("http://foo.com:33", UrlBuilder.forHost("http", "foo.com", 33).toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimplePath() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.pathSegment("seg1").pathSegment("seg2");
|
||||
assertUrlEquals("http://foo.com/seg1/seg2", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathWithReserved() throws Exception {
|
||||
// RFC 1738 S3.3
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.pathSegment("seg/;?ment").pathSegment("seg=&2");
|
||||
assertUrlEquals("http://foo.com/seg%2F%3B%3Fment/seg=&2", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathSegments() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.pathSegments("seg1", "seg2", "seg3");
|
||||
assertUrlEquals("http://foo.com/seg1/seg2/seg3", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatrixWithoutPathHasLeadingSlash() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.matrixParam("foo", "bar");
|
||||
assertUrlEquals("http://foo.com/;foo=bar", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatrixWithReserved() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com")
|
||||
.pathSegment("foo")
|
||||
.matrixParam("foo", "bar")
|
||||
.matrixParam("res;=?#/erved", "value")
|
||||
.pathSegment("baz");
|
||||
assertUrlEquals("http://foo.com/foo;foo=bar;res%3B%3D%3F%23%2Ferved=value/baz", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUrlEncodedPathSegmentUtf8() throws Exception {
|
||||
// 1 UTF-16 char
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.pathSegment("snowman").pathSegment("\u2603");
|
||||
assertUrlEquals("http://foo.com/snowman/%E2%98%83", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUrlEncodedPathSegmentUtf8SurrogatePair() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
// musical G clef: 1d11e, has to be represented in surrogate pair form
|
||||
ub.pathSegment("clef").pathSegment("\ud834\udd1e");
|
||||
assertUrlEquals("http://foo.com/clef/%F0%9D%84%9E", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParamNoPath() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.queryParam("foo", "bar");
|
||||
String s = ub.toUrlString();
|
||||
assertUrlEquals("http://foo.com?foo=bar", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParamsDuplicated() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.queryParam("foo", "bar");
|
||||
ub.queryParam("foo", "bar2");
|
||||
ub.queryParam("baz", "quux");
|
||||
ub.queryParam("baz", "quux2");
|
||||
assertUrlEquals("http://foo.com?foo=bar&foo=bar2&baz=quux&baz=quux2", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeQueryParams() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.queryParam("foo", "bar&=#baz");
|
||||
ub.queryParam("foo", "bar?/2");
|
||||
assertUrlEquals("http://foo.com?foo=bar%26%3D%23baz&foo=bar?/2", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeQueryParamWithSpaceAndPlus() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.queryParam("foo", "spa ce");
|
||||
ub.queryParam("fo+o", "plus+");
|
||||
assertUrlEquals("http://foo.com?foo=spa%20ce&fo%2Bo=plus%2B", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlusInVariousParts() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
|
||||
ub.pathSegment("has+plus")
|
||||
.matrixParam("plusMtx", "pl+us")
|
||||
.queryParam("plusQp", "pl+us")
|
||||
.fragment("plus+frag");
|
||||
|
||||
assertUrlEquals("http://foo.com/has+plus;plusMtx=pl+us?plusQp=pl%2Bus#plus+frag", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFragment() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com");
|
||||
ub.queryParam("foo", "bar");
|
||||
ub.fragment("#frag/?");
|
||||
assertUrlEquals("http://foo.com?foo=bar#%23frag/?", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllParts() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("https", "foo.bar.com", 3333);
|
||||
ub.pathSegment("foo");
|
||||
ub.pathSegment("bar");
|
||||
ub.matrixParam("mtx1", "val1");
|
||||
ub.matrixParam("mtx2", "val2");
|
||||
ub.queryParam("q1", "v1");
|
||||
ub.queryParam("q2", "v2");
|
||||
ub.fragment("zomg it's a fragment");
|
||||
|
||||
assertEquals("https://foo.bar.com:3333/foo/bar;mtx1=val1;mtx2=val2?q1=v1&q2=v2#zomg%20it's%20a%20fragment",
|
||||
ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv4Literal() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "127.0.0.1");
|
||||
assertUrlEquals("http://127.0.0.1", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadIPv4LiteralDoesntChoke() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "300.100.50.1");
|
||||
assertUrlEquals("http://300.100.50.1", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6LiteralLocalhost() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "[::1]");
|
||||
assertUrlEquals("http://[::1]", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6Literal() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "[2001:db8:85a3::8a2e:370:7334]");
|
||||
assertUrlEquals("http://[2001:db8:85a3::8a2e:370:7334]", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedRegNameSingleByte() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "host?name;");
|
||||
assertUrlEquals("http://host%3Fname;", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedRegNameMultiByte() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "snow\u2603man");
|
||||
assertUrlEquals("http://snow%E2%98%83man", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceTrailingSlash() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("https", "foo.com").forceTrailingSlash().pathSegments("a", "b", "c");
|
||||
|
||||
assertUrlEquals("https://foo.com/a/b/c/", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceTrailingSlashWithQueryParams() throws Exception {
|
||||
UrlBuilder ub =
|
||||
UrlBuilder.forHost("https", "foo.com").forceTrailingSlash().pathSegments("a", "b", "c").queryParam("foo", "bar");
|
||||
|
||||
assertUrlEquals("https://foo.com/a/b/c/?foo=bar", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceTrailingSlashNoPathSegmentsWithMatrixParams() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("https", "foo.com").forceTrailingSlash().matrixParam("m1", "v1");
|
||||
|
||||
assertUrlEquals("https://foo.com/;m1=v1/", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntermingledMatrixParamsAndPathSegments() throws Exception {
|
||||
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com")
|
||||
.pathSegments("seg1", "seg2")
|
||||
.matrixParam("m1", "v1")
|
||||
.pathSegment("seg3")
|
||||
.matrixParam("m2", "v2");
|
||||
|
||||
assertUrlEquals("http://foo.com/seg1/seg2;m1=v1/seg3;m2=v2", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEverything() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
String orig =
|
||||
"https://foo.bar.com:3333/foo/ba%20r;mtx1=val1;mtx2=val%202/seg%203;m2=v2?q1=v1&q2=v%202#zomg%20it's%20a%20fragment";
|
||||
assertUrlBuilderRoundtrip(orig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPath() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPathAndSlash() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/", "http://foo.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithPort() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com:1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPathSegent() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo//", "http://foo.com/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedHost() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://f%20oo.com/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedPathSegment() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo/b%20ar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedMatrixParam() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo;m1=v1;m%202=v%202");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedQueryParam() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo?q%201=v%202&q2=v2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedQueryParamDelimiter() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo?q1=%3Dv1&%26q2=v2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEncodedFragment() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo#b%20ar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithMalformedMatrixPair() throws MalformedURLException, CharacterCodingException {
|
||||
try {
|
||||
UrlBuilder.fromUrl("http://foo.com/foo;m1=v1=v2");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Malformed matrix param: <m1=v1=v2>", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPathSegmentWithMatrixParams() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo/;m1=v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPathWithMatrixParams() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/;m1=v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithEmptyPathWithMultipleMatrixParams() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/;m1=v1;m2=v2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlWithPathSegmentEndingWithSemicolon() throws URISyntaxException, CharacterCodingException, MalformedURLException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo;", "http://foo.com/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPercentDecodeInvalidPair() throws MalformedURLException, CharacterCodingException {
|
||||
try {
|
||||
UrlBuilder.fromUrl("http://foo.com/fo%2o");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e.getMessage().startsWith("Malformed escape pair"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlMalformedQueryParamMultiValues() throws MalformedURLException, CharacterCodingException, URISyntaxException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo?q1=v1=v2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlMalformedQueryParamNoValue() throws MalformedURLException, CharacterCodingException, URISyntaxException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo?q1=v1&q2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUrlUnstructuredQueryWithEscapedChars() throws MalformedURLException, CharacterCodingException, URISyntaxException {
|
||||
assertUrlBuilderRoundtrip("http://foo.com/foo?query==&%23");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCantUseQueryParamAfterQuery() {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com").unstructuredQuery("q");
|
||||
|
||||
try {
|
||||
ub.queryParam("foo", "bar");
|
||||
fail();
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("Cannot call queryParam() when this already has an unstructured query specified",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCantUseQueryAfterQueryParam() {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "foo.com").queryParam("foo", "bar");
|
||||
|
||||
try {
|
||||
ub.unstructuredQuery("q");
|
||||
|
||||
fail();
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("Cannot call unstructuredQuery() when this already has queryParam pairs specified",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnstructuredQueryWithNoSpecialChars() throws Exception {
|
||||
assertUrlEquals("http://foo.com?q", UrlBuilder.forHost("http", "foo.com").unstructuredQuery("q").toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnstructuredQueryWithOkSpecialChars() throws Exception {
|
||||
assertUrlEquals("http://foo.com?q?/&=", UrlBuilder.forHost("http", "foo.com").unstructuredQuery("q?/&=").toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnstructuredQueryWithEscapedSpecialChars() throws Exception {
|
||||
assertUrlEquals("http://foo.com?q%23%2B", UrlBuilder.forHost("http", "foo.com").unstructuredQuery("q#+").toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearQueryRemovesQueryParam() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "host")
|
||||
.queryParam("foo", "bar")
|
||||
.clearQuery();
|
||||
assertUrlEquals("http://host", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearQueryRemovesUnstructuredQuery() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "host")
|
||||
.unstructuredQuery("foobar")
|
||||
.clearQuery();
|
||||
assertUrlEquals("http://host", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearQueryAfterQueryParamAllowsQuery() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "host")
|
||||
.queryParam("foo", "bar")
|
||||
.clearQuery()
|
||||
.unstructuredQuery("foobar");
|
||||
assertUrlEquals("http://host?foobar", ub.toUrlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearQueryAfterQueryAllowsQueryParam() throws Exception {
|
||||
UrlBuilder ub = UrlBuilder.forHost("http", "host")
|
||||
.unstructuredQuery("foobar")
|
||||
.clearQuery()
|
||||
.queryParam("foo", "bar");
|
||||
assertUrlEquals("http://host?foo=bar", ub.toUrlString());
|
||||
}
|
||||
|
||||
private void assertUrlBuilderRoundtrip(String url) throws MalformedURLException, CharacterCodingException, URISyntaxException {
|
||||
assertUrlBuilderRoundtrip(url, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param origUrl the url that will be used to create a URL
|
||||
* @param finalUrl the URL string it should end up as
|
||||
*/
|
||||
private void assertUrlBuilderRoundtrip(String origUrl, String finalUrl) throws MalformedURLException, CharacterCodingException, URISyntaxException {
|
||||
URL url = URI.create(origUrl).toURL();
|
||||
assertUrlEquals(finalUrl, UrlBuilder.fromUrl(url).toUrlString());
|
||||
}
|
||||
|
||||
private static void assertUrlEquals(String expected, String actual) throws URISyntaxException, MalformedURLException {
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(expected, new URI(actual).toString());
|
||||
assertEquals(expected, URI.create(actual).toURL().toString());
|
||||
}
|
||||
}
|
8
oai-client/src/test/resources/logging.properties
Normal file
8
oai-client/src/test/resources/logging.properties
Normal file
|
@ -0,0 +1,8 @@
|
|||
handlers=java.util.logging.ConsoleHandler
|
||||
.level=ALL
|
||||
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
|
||||
java.util.logging.ConsoleHandler.level=ALL
|
||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
||||
jdk.event.security.level=INFO
|
||||
jdk.internal.httpclient.hpack.debug.level=INFO
|
||||
jdk.internal.httpclient.debug.level=INFO
|
|
@ -1,6 +1,6 @@
|
|||
dependencies {
|
||||
api "org.xbib:content-core:${project.property('xbib-content.version')}"
|
||||
api "org.xbib:content-rdf:${project.property('xbib-content.version')}"
|
||||
api "org.xbib:content-resource:${project.property('xbib-content.version')}"
|
||||
api "org.xbib:content-xml:${project.property('xbib-content.version')}"
|
||||
api libs.content.core
|
||||
api libs.content.rdf
|
||||
api libs.content.resource
|
||||
api libs.content.xml
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ module org.xbib.oai {
|
|||
exports org.xbib.oai.rdf;
|
||||
exports org.xbib.oai.util;
|
||||
exports org.xbib.oai.xml;
|
||||
requires org.xbib.content.core;
|
||||
requires org.xbib.content.rdf;
|
||||
requires org.xbib.content.resource;
|
||||
requires org.xbib.content.xml;
|
||||
requires transitive org.xbib.content.rdf;
|
||||
requires java.xml;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@ package org.xbib.oai.util;
|
|||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RecordHeader {
|
||||
|
||||
private String identifier;
|
||||
|
@ -13,6 +10,9 @@ public class RecordHeader {
|
|||
|
||||
private String set;
|
||||
|
||||
public RecordHeader() {
|
||||
}
|
||||
|
||||
public RecordHeader setIdentifier(String identifier) {
|
||||
this.identifier = identifier;
|
||||
return this;
|
||||
|
|
|
@ -3,13 +3,13 @@ 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() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleMetadataHandler setHeader(RecordHeader header) {
|
||||
this.header = header;
|
||||
|
|
|
@ -22,9 +22,6 @@ 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 static final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
|
||||
|
@ -45,6 +42,9 @@ public class XmlSimpleMetadataHandler extends SimpleMetadataHandler implements O
|
|||
|
||||
private boolean needToCallStartDocument = false;
|
||||
|
||||
public XmlSimpleMetadataHandler() {
|
||||
}
|
||||
|
||||
public XmlSimpleMetadataHandler setWriter(Writer writer) throws XMLStreamException {
|
||||
this.writer = writer;
|
||||
outputFactory.setProperty("javax.xml.stream.isRepairingNamespaces", Boolean.TRUE);
|
||||
|
|
16
oai-server/src/main/java/module-info.java
Normal file
16
oai-server/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
import org.xbib.oai.server.OAIServer;
|
||||
|
||||
module org.xbib.oai.server.main {
|
||||
requires java.logging;
|
||||
requires transitive java.xml;
|
||||
requires transitive org.xbib.oai;
|
||||
exports org.xbib.oai.server;
|
||||
exports org.xbib.oai.server.getrecord;
|
||||
exports org.xbib.oai.server.identify;
|
||||
exports org.xbib.oai.server.listidentifiers;
|
||||
exports org.xbib.oai.server.listmetadataformats;
|
||||
exports org.xbib.oai.server.listrecords;
|
||||
exports org.xbib.oai.server.listsets;
|
||||
exports org.xbib.oai.server.verb;
|
||||
uses OAIServer;
|
||||
}
|
|
@ -8,9 +8,6 @@ import java.time.Instant;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractOAIRequest implements OAIRequest {
|
||||
|
||||
private String path;
|
||||
|
|
|
@ -11,6 +11,9 @@ public abstract class AbstractOAIResponse implements OAIResponse {
|
|||
|
||||
private XMLEventConsumer consumer;
|
||||
|
||||
public AbstractOAIResponse() {
|
||||
}
|
||||
|
||||
public AbstractOAIResponse setConsumer(XMLEventConsumer consumer) {
|
||||
this.consumer = consumer;
|
||||
return this;
|
||||
|
|
|
@ -11,9 +11,6 @@ import java.util.ServiceLoader;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class OAIServiceFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(OAIServiceFactory.class.getName());
|
||||
|
|
|
@ -15,13 +15,11 @@ import org.xbib.oai.server.listsets.ListSetsServerRequest;
|
|||
import org.xbib.oai.server.listsets.ListSetsServerResponse;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PropertiesOAIServer implements OAIServer {
|
||||
|
||||
private static final String ADAPTER_URI = "uri";
|
||||
|
@ -42,7 +40,7 @@ public class PropertiesOAIServer implements OAIServer {
|
|||
|
||||
private static final String GRANULARITY = "identify.granularity";
|
||||
|
||||
private Properties properties;
|
||||
private final Properties properties;
|
||||
|
||||
public PropertiesOAIServer(Properties properties) {
|
||||
this.properties = properties;
|
||||
|
@ -51,7 +49,7 @@ public class PropertiesOAIServer implements OAIServer {
|
|||
@Override
|
||||
public URL getURL() {
|
||||
try {
|
||||
return new URL(properties.getProperty(ADAPTER_URI).trim());
|
||||
return URI.create(properties.getProperty(ADAPTER_URI).trim()).toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
//
|
||||
}
|
||||
|
@ -70,7 +68,7 @@ public class PropertiesOAIServer implements OAIServer {
|
|||
@Override
|
||||
public URL getBaseURL() {
|
||||
try {
|
||||
return new URL(properties.getProperty(BASE_URL));
|
||||
return URI.create(properties.getProperty(BASE_URL)).toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.getrecord;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class GetRecordServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public GetRecordServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.xbib.oai.server.getrecord;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class GetRecordServerResponse extends AbstractOAIResponse {
|
||||
|
||||
public GetRecordServerResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI get record.
|
||||
*/
|
||||
package org.xbib.oai.server.getrecord;
|
|
@ -2,8 +2,8 @@ package org.xbib.oai.server.identify;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IdentifyServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public IdentifyServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IdentifyServerResponse extends AbstractOAIResponse {
|
||||
|
||||
private String repositoryName;
|
||||
|
@ -18,7 +15,7 @@ public class IdentifyServerResponse extends AbstractOAIResponse {
|
|||
|
||||
private String protocolVersion;
|
||||
|
||||
private List<String> adminEmails = new ArrayList<>();
|
||||
private final List<String> adminEmails = new ArrayList<>();
|
||||
|
||||
private Date earliestDatestamp;
|
||||
|
||||
|
@ -28,6 +25,9 @@ public class IdentifyServerResponse extends AbstractOAIResponse {
|
|||
|
||||
private String compression;
|
||||
|
||||
public IdentifyServerResponse() {
|
||||
}
|
||||
|
||||
public void setRepositoryName(String repositoryName) {
|
||||
this.repositoryName = repositoryName;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI identify verb.
|
||||
*/
|
||||
package org.xbib.oai.server.identify;
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listidentifiers;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListIdentifiersServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public ListIdentifiersServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listidentifiers;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListIdentifiersServerResponse extends AbstractOAIResponse {
|
||||
|
||||
public ListIdentifiersServerResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list identifiers verb.
|
||||
*/
|
||||
package org.xbib.oai.server.listidentifiers;
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listmetadataformats;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListMetadataFormatsServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public ListMetadataFormatsServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listmetadataformats;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListMetadataFormatsServerResponse extends AbstractOAIResponse {
|
||||
|
||||
public ListMetadataFormatsServerResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list metadata formats verb.
|
||||
*/
|
||||
package org.xbib.oai.server.listmetadataformats;
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listrecords;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListRecordsServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public ListRecordsServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ import org.xbib.oai.server.AbstractOAIResponse;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListRecordsServerResponse extends AbstractOAIResponse {
|
||||
|
||||
private String error;
|
||||
|
@ -15,6 +12,9 @@ public class ListRecordsServerResponse extends AbstractOAIResponse {
|
|||
|
||||
private long expire;
|
||||
|
||||
public ListRecordsServerResponse() {
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list records verb.
|
||||
*/
|
||||
package org.xbib.oai.server.listrecords;
|
|
@ -2,9 +2,8 @@ package org.xbib.oai.server.listsets;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListSetsServerRequest extends AbstractOAIRequest {
|
||||
|
||||
public ListSetsServerRequest() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.xbib.oai.server.listsets;
|
|||
|
||||
import org.xbib.oai.server.AbstractOAIResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ListSetsServerResponse extends AbstractOAIResponse {
|
||||
|
||||
public ListSetsServerResponse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* OAI list sets verb.
|
||||
*/
|
||||
package org.xbib.oai.server.listsets;
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Classes for OAI server.
|
||||
*/
|
||||
package org.xbib.oai.server;
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Classes for OAI server verbs.
|
||||
*/
|
||||
package org.xbib.oai.server.verb;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue