refactor to BERReader/BERWriter interface, add locking

This commit is contained in:
Jörg Prante 2018-09-07 22:59:21 +02:00
parent e9b3b4fa5a
commit 00412e7577
26 changed files with 687 additions and 406 deletions

View file

@ -57,7 +57,7 @@ public final class ASN1BitString extends ASN1Any {
} }
if (berEncoding instanceof BERPrimitive) { if (berEncoding instanceof BERPrimitive) {
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
if (encoding.length < 1) { if (encoding.length < 1) {
throw new ASN1EncodingException("ASN1 BIT STRING: invalid encoding, length = " + encoding.length); throw new ASN1EncodingException("ASN1 BIT STRING: invalid encoding, length = " + encoding.length);
} }

View file

@ -55,7 +55,7 @@ public final class ASN1Boolean extends ASN1Any {
} }
if (berEncoding instanceof BERPrimitive) { if (berEncoding instanceof BERPrimitive) {
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
if (encoding.length != 1) { if (encoding.length != 1) {
throw new ASN1EncodingException("ASN.1 BOOLEAN: invalid encoding, length = " + encoding.length); throw new ASN1EncodingException("ASN.1 BOOLEAN: invalid encoding, length = " + encoding.length);
} }

View file

@ -61,7 +61,7 @@ public final class ASN1Enumerated extends ASN1Any {
throw new ASN1EncodingException("bad form, constructed"); throw new ASN1EncodingException("bad form, constructed");
} }
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
if (encoding.length < 1) { if (encoding.length < 1) {
throw new ASN1EncodingException("invalid encoding, length = " + encoding.length); throw new ASN1EncodingException("invalid encoding, length = " + encoding.length);
} }

View file

@ -63,7 +63,7 @@ public final class ASN1Integer extends ASN1Any {
throw new ASN1EncodingException("bad form, constructed"); throw new ASN1EncodingException("bad form, constructed");
} }
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
if (encoding.length < 1) { if (encoding.length < 1) {
throw new ASN1EncodingException("invalid encoding, length = " + encoding.length); throw new ASN1EncodingException("invalid encoding, length = " + encoding.length);
} }

View file

@ -58,7 +58,7 @@ public final class ASN1ObjectIdentifier extends ASN1Any {
throw new ASN1EncodingException("bad form, constructed"); throw new ASN1EncodingException("bad form, constructed");
} }
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
if (encoding.length < 2) { if (encoding.length < 2) {
throw new ASN1EncodingException("invalid encoding, length = " + throw new ASN1EncodingException("invalid encoding, length = " +
encoding.length); encoding.length);

View file

@ -75,7 +75,7 @@ public class ASN1OctetString extends ASN1Any {
} }
if (berEncoding instanceof BERPrimitive) { if (berEncoding instanceof BERPrimitive) {
BERPrimitive ber = (BERPrimitive) berEncoding; BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek(); int[] encoding = ber.getContentOctets();
StringBuilder buf = new StringBuilder(encoding.length); StringBuilder buf = new StringBuilder(encoding.length);
for (int anEncoding : encoding) { for (int anEncoding : encoding) {
buf.append((char) (anEncoding & 0x00ff)); buf.append((char) (anEncoding & 0x00ff));

View file

@ -28,8 +28,7 @@ public class ASN1VideotexString extends ASN1OctetString {
* @param checkTag If true, it checks the tag. Use false if is implicitly tagged. * @param checkTag If true, it checks the tag. Use false if is implicitly tagged.
* @throws ASN1Exception If the BER encoding is incorrect. * @throws ASN1Exception If the BER encoding is incorrect.
*/ */
public ASN1VideotexString(BEREncoding ber, boolean checkTag) public ASN1VideotexString(BEREncoding ber, boolean checkTag) throws ASN1Exception {
throws ASN1Exception {
super(ber, false); super(ber, false);
if (checkTag && (ber.tagGet() != VIDEOTEX_STRING_TAG || ber.tagTypeGet() != BEREncoding.UNIVERSAL_TAG)) { if (checkTag && (ber.tagGet() != VIDEOTEX_STRING_TAG || ber.tagTypeGet() != BEREncoding.UNIVERSAL_TAG)) {
throw new ASN1EncodingException("bad BER: tag=" + ber.tagGet() + throw new ASN1EncodingException("bad BER: tag=" + ber.tagGet() +

View file

@ -1,8 +1,5 @@
package org.xbib.asn1; package org.xbib.asn1;
import java.io.IOException;
import java.io.OutputStream;
/** /**
* BERConstructed. * BERConstructed.
* This class represents a BER encoded ASN.1 object which is * This class represents a BER encoded ASN.1 object which is
@ -38,21 +35,8 @@ public class BERConstructed extends BEREncoding {
contentElements = elements; contentElements = elements;
} }
/** public BEREncoding[] getContentElements() {
* This method outputs the encoded octets for this object return contentElements;
* to the output stream.
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that
* the data has been written out.
*
* @param dest OutputStream to write encoding to.
*/
@Override
public void output(OutputStream dest) throws IOException {
outputHead(dest);
for (BEREncoding contentElement : contentElements) {
contentElement.output(dest);
}
} }
/** /**

View file

@ -1,11 +1,5 @@
package org.xbib.asn1; package org.xbib.asn1;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/** /**
* This class represents a BER (Basic Encoding Rules) encoded ASN.1 object. * This class represents a BER (Basic Encoding Rules) encoded ASN.1 object.
* This is an abstract base class from which there are two specific * This is an abstract base class from which there are two specific
@ -24,8 +18,6 @@ import java.util.List;
*/ */
public abstract class BEREncoding { public abstract class BEREncoding {
private static final String ERROR = "Unexpected end in BER encoding";
/** /**
* Constant for indicating UNIVERSAL tag type. The value matches * Constant for indicating UNIVERSAL tag type. The value matches
* the BER bit encoding. Universal tags are for types defined in * the BER bit encoding. Universal tags are for types defined in
@ -53,7 +45,6 @@ public abstract class BEREncoding {
*/ */
public static final int PRIVATE_TAG = 0xC0; public static final int PRIVATE_TAG = 0xC0;
private static final int MAX_BER_SIZE = 65536 * 4;
/** /**
* The tag type of this BER encoded object. This value must be * The tag type of this BER encoded object. This value must be
* the same as that encoded in the identiferEncoding. * the same as that encoded in the identiferEncoding.
@ -86,153 +77,25 @@ public abstract class BEREncoding {
*/ */
private int[] lengthEncoding; private int[] lengthEncoding;
/** public int[] getIdentifierEncoding() {
* The public wrapping for doInput() method. return identifierEncoding;
*
* @param inputStream the InputStream to read the raw BER from.
* @return Returns the next complete BEREncoding object read
* in from the input stream. Returns null if the
* end has been reached.
* @throws ASN1Exception If data does not represent a BER encoding
* @throws IOException On input I/O error
*/
public static BEREncoding input(InputStream inputStream) throws IOException {
int[] numBytesRead = new int[1];
numBytesRead[0] = 0;
return doInput(inputStream, numBytesRead);
} }
/** public int[] getLengthEncoding() {
* Constructs a complete BER encoding object from octets read in from return lengthEncoding;
* an InputStream.
* This routine handles all forms of encoding, including the
* indefite-length method. The length is always known with this
* class. With indefinite-length encodings,
* the end-of-contents octets are not included in the returned
* object (i.e. the returned the raw BER is converted to an object
* which is in the definite-length form).
*
* @param numBytesRead a counter for all read bytes.
* @param inputStream the InputStream to read the raw BER from.
* @return the next complete BEREncoding object read
* in from the input stream. Returns null if the
* end has been reached.
* @throws IOException If data does not represent a BER encoding or input I/O error
*/
protected static BEREncoding doInput(InputStream inputStream, int[] numBytesRead) throws IOException {
int octet = inputStream.read();
if (octet < 0) {
return null;
}
numBytesRead[0]++;
int tagType = octet & 0xC0;
boolean isCons = false;
if ((octet & 0x20) != 0) {
isCons = true;
}
int tag = octet & 0x1F;
if (tag == 0x1F) {
tag = 0;
do {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
tag <<= 7;
tag |= (octet & 0x7F);
} while ((octet & 0x80) != 0);
}
int length;
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
if ((octet & 0x80) != 0) {
if ((octet & 0x7f) == 0) {
length = -1;
if (!isCons) {
throw new ASN1EncodingException("BER encoding corrupted primitive");
}
} else {
if (4 < (octet & 0x7f)) {
throw new ASN1EncodingException("BER encoding too long");
}
length = 0;
for (int numBytes = octet & 0x7f; 0 < numBytes; numBytes--) {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
length <<= 8;
length |= (octet & 0xff);
}
if (length < 0 || MAX_BER_SIZE < length) {
throw new ASN1EncodingException("BER encoding too long");
}
}
} else {
length = octet & 0x7F;
}
if (!isCons) {
int[] contents = new int[length];
for (int x = 0; x < length; x++) {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
contents[x] = octet;
}
return new BERPrimitive(tagType, tag, contents);
} else {
List<BEREncoding> chunks = new ArrayList<>();
int totalRead = 0;
if (0 <= length) {
while (totalRead < length) {
int currentRead = numBytesRead[0];
BEREncoding chunk = BEREncoding.doInput(inputStream, numBytesRead);
if (chunk == null) {
throw new ASN1EncodingException(ERROR);
}
chunks.add(chunk);
totalRead += numBytesRead[0] - currentRead;
}
} else {
while (true) {
BEREncoding chunk = BEREncoding.doInput(inputStream, numBytesRead);
if (chunk == null) {
throw new ASN1EncodingException(ERROR);
}
if (chunk.iTag == 0 && chunk.iTagType == BEREncoding.UNIVERSAL_TAG && chunk.iTotalLength == 2) {
break;
} else {
chunks.add(chunk);
}
}
}
int numElements = chunks.size();
BEREncoding[] parts = new BEREncoding[numElements];
for (int x = 0; x < numElements; x++) {
parts[x] = chunks.get(x);
}
return new BERConstructed(tagType, tag, parts);
}
} }
/** public int getITag() {
* Outputs the BER object to an OutputStream. This method should work return iTag;
* with any OutputStream, whether it is from a socket, file, etc. }
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that public int getITagType() {
* the data has been written out. return iTagType;
* }
* @param dest - the OutputStream to write the encoding to.
* @throws IOException On output I/O error public int getITotalLength() {
*/ return iTotalLength;
public abstract void output(OutputStream dest) throws IOException; }
/** /**
* Returns the BER encoded object as an array of bytes. This routine * Returns the BER encoded object as an array of bytes. This routine
@ -289,27 +152,6 @@ public abstract class BEREncoding {
iTotalLength = identifierEncoding.length + lengthEncoding.length + length; iTotalLength = identifierEncoding.length + lengthEncoding.length + length;
} }
/*
* This is a protected routine used for outputting an array of
* integers, interpreted as bytes, to an OutputStream. It is used
* by the superclasses to implement the "output" method.
*/
protected void outputBytes(int[] data, OutputStream dest) throws IOException {
for (int aData : data) {
dest.write(aData);
}
}
/*
* This is a protected method used to output the encoded identifier
* and length octets to an OutputStream. It is used by the superclasses
* to implement the "output" method.
*/
protected void outputHead(OutputStream dest) throws IOException {
outputBytes(identifierEncoding, dest);
outputBytes(lengthEncoding, dest);
}
/* /*
* Internal protected method fills in the data array (starting from index * Internal protected method fills in the data array (starting from index
* position offset) with the encoding for the identifier and length. * position offset) with the encoding for the identifier and length.

View file

@ -1,8 +1,5 @@
package org.xbib.asn1; package org.xbib.asn1;
import java.io.IOException;
import java.io.OutputStream;
/** /**
* This class represents a primitive ASN.1 object encoded * This class represents a primitive ASN.1 object encoded
* according to the Basic Encoding Rules. * according to the Basic Encoding Rules.
@ -39,8 +36,7 @@ public class BERPrimitive extends BEREncoding {
* @see org.xbib.asn1.BEREncoding#CONTEXT_SPECIFIC_TAG * @see org.xbib.asn1.BEREncoding#CONTEXT_SPECIFIC_TAG
* @see org.xbib.asn1.BEREncoding#PRIVATE_TAG * @see org.xbib.asn1.BEREncoding#PRIVATE_TAG
*/ */
BERPrimitive(int asn1Class, int tag, int[] contents) public BERPrimitive(int asn1Class, int tag, int[] contents) throws ASN1Exception {
throws ASN1Exception {
init(asn1Class, false, tag, contents.length); init(asn1Class, false, tag, contents.length);
contentsOctets = contents; contentsOctets = contents;
} }
@ -49,24 +45,10 @@ public class BERPrimitive extends BEREncoding {
* This method allows the content octets to be examined. * This method allows the content octets to be examined.
* Once again, only the ASN.1 standard objects should be using this. * Once again, only the ASN.1 standard objects should be using this.
*/ */
int[] peek() { public int[] getContentOctets() {
return contentsOctets; return contentsOctets;
} }
/**
* This method outputs the encoded octets to the destination OutputStream.
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that
* the data has been written out.
*
* @param dest - OutputStream to write encoding to.
*/
@Override
public void output(OutputStream dest) throws IOException {
outputHead(dest);
outputBytes(contentsOctets, dest);
}
/** /**
* Returns a new String object representing this BER encoded * Returns a new String object representing this BER encoded
* ASN.1 object's value. * ASN.1 object's value.

View file

@ -0,0 +1,12 @@
package org.xbib.asn1.io;
import org.xbib.asn1.BEREncoding;
import java.io.IOException;
public interface BERReader extends AutoCloseable {
BEREncoding read() throws IOException;
void close() throws IOException;
}

View file

@ -0,0 +1,12 @@
package org.xbib.asn1.io;
import org.xbib.asn1.BEREncoding;
import java.io.IOException;
public interface BERWriter extends AutoCloseable {
void write(BEREncoding ber) throws IOException;
void close() throws IOException;
}

View file

@ -0,0 +1,166 @@
package org.xbib.asn1.io;
import org.xbib.asn1.ASN1EncodingException;
import org.xbib.asn1.ASN1Exception;
import org.xbib.asn1.BERConstructed;
import org.xbib.asn1.BEREncoding;
import org.xbib.asn1.BERPrimitive;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class InputStreamBERReader implements BERReader {
private static final String ERROR = "Unexpected end in BER encoding";
private static final int MAX_BER_SIZE = 65536 * 4;
private final InputStream inputStream;
public InputStreamBERReader(InputStream inputStream) {
this.inputStream = inputStream;
}
/**
* The public wrapping for doInput() method.
*
* @return returns the next complete BEREncoding object read
* in from the input stream. Returns null if the
* end has been reached.
* @throws ASN1Exception If data does not represent a BER encoding
* @throws IOException On input I/O error
*/
@Override
public BEREncoding read() throws IOException {
int[] numBytesRead = new int[1];
numBytesRead[0] = 0;
return doInput(numBytesRead);
}
@Override
public void close() throws IOException {
inputStream.close();
}
/**
* Constructs a complete BER encoding object from octets read in from
* an InputStream.
* This routine handles all forms of encoding, including the
* indefite-length method. The length is always known with this
* class. With indefinite-length encodings,
* the end-of-contents octets are not included in the returned
* object (i.e. the returned the raw BER is converted to an object
* which is in the definite-length form).
*
* @param numBytesRead a counter for all read bytes.
* @return the next complete BEREncoding object read
* in from the input stream. Returns null if the
* end has been reached.
* @throws IOException If data does not represent a BER encoding or input I/O error
*/
private BEREncoding doInput(int[] numBytesRead) throws IOException {
int octet = inputStream.read();
if (octet < 0) {
return null;
}
numBytesRead[0]++;
int tagType = octet & 0xC0;
boolean isCons = false;
if ((octet & 0x20) != 0) {
isCons = true;
}
int tag = octet & 0x1F;
if (tag == 0x1F) {
tag = 0;
do {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
tag <<= 7;
tag |= (octet & 0x7F);
} while ((octet & 0x80) != 0);
}
int length;
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
if ((octet & 0x80) != 0) {
if ((octet & 0x7f) == 0) {
length = -1;
if (!isCons) {
throw new ASN1EncodingException("BER encoding corrupted primitive");
}
} else {
if (4 < (octet & 0x7f)) {
throw new ASN1EncodingException("BER encoding too long");
}
length = 0;
for (int numBytes = octet & 0x7f; 0 < numBytes; numBytes--) {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
length <<= 8;
length |= (octet & 0xff);
}
if (length < 0 || MAX_BER_SIZE < length) {
throw new ASN1EncodingException("BER encoding too long");
}
}
} else {
length = octet & 0x7F;
}
if (!isCons) {
int[] contents = new int[length];
for (int x = 0; x < length; x++) {
octet = inputStream.read();
if (octet < 0) {
throw new ASN1EncodingException(ERROR);
}
numBytesRead[0]++;
contents[x] = octet;
}
return new BERPrimitive(tagType, tag, contents);
} else {
List<BEREncoding> chunks = new ArrayList<>();
int totalRead = 0;
if (0 <= length) {
while (totalRead < length) {
int currentRead = numBytesRead[0];
BEREncoding chunk = doInput( numBytesRead);
if (chunk == null) {
throw new ASN1EncodingException(ERROR);
}
chunks.add(chunk);
totalRead += numBytesRead[0] - currentRead;
}
} else {
while (true) {
BEREncoding chunk = doInput(numBytesRead);
if (chunk == null) {
throw new ASN1EncodingException(ERROR);
}
if (chunk.getITag() == 0 && chunk.getITagType() == BEREncoding.UNIVERSAL_TAG &&
chunk.getITotalLength() == 2) {
break;
} else {
chunks.add(chunk);
}
}
}
int numElements = chunks.size();
BEREncoding[] parts = new BEREncoding[numElements];
for (int x = 0; x < numElements; x++) {
parts[x] = chunks.get(x);
}
return new BERConstructed(tagType, tag, parts);
}
}
}

View file

@ -0,0 +1,96 @@
package org.xbib.asn1.io;
import org.xbib.asn1.BERConstructed;
import org.xbib.asn1.BEREncoding;
import org.xbib.asn1.BERPrimitive;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamBERWriter implements BERWriter {
private final OutputStream outputStream;
private final boolean autoflush;
public OutputStreamBERWriter(OutputStream outputStream) {
this(outputStream, true);
}
public OutputStreamBERWriter(OutputStream outputStream, boolean autoflush) {
this.outputStream = outputStream;
this.autoflush = autoflush;
}
/**
* Outputs the BER object to an OutputStream. This method should work
* with any OutputStream, whether it is from a socket, file, etc.
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that
* the data has been written out.
*
* @throws IOException On output I/O error
*/
@Override
public void write(BEREncoding ber) throws IOException {
if (ber instanceof BERPrimitive) {
writeBERPrimitive((BERPrimitive) ber);
} else if (ber instanceof BERConstructed) {
writeBERConstructed((BERConstructed) ber);
}
if (autoflush) {
outputStream.flush();
}
}
@Override
public void close() throws IOException {
outputStream.close();
}
/**
* This method outputs the encoded octets for this object
* to the output stream.
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that
* the data has been written out.
*/
private void writeBERConstructed(BERConstructed ber) throws IOException {
outputHead(ber);
for (BEREncoding contentElement : ber.getContentElements()) {
write(contentElement);
}
}
/**
* This method outputs the encoded octets to the destination OutputStream.
* Note: the output is not flushed, so you <strong>must</strong> explicitly
* flush the output stream after calling this method to ensure that
* the data has been written out.
*/
private void writeBERPrimitive(BERPrimitive ber) throws IOException {
outputHead(ber);
outputBytes(ber.getContentOctets());
}
/*
* This is a protected method used to output the encoded identifier
* and length octets. It is used by the superclasses
* to implement the "output" method.
*/
private void outputHead(BEREncoding ber) throws IOException {
outputBytes(ber.getIdentifierEncoding());
outputBytes(ber.getLengthEncoding());
}
/*
* This is a protected routine used for outputting an array of
* integers, interpreted as bytes, to an OutputStream. It is used
* by the superclasses to implement the "output" method.
*/
private void outputBytes(int[] data) throws IOException {
for (int aData : data) {
outputStream.write(aData);
}
}
}

View file

@ -39,12 +39,18 @@ subprojects {
wagon "org.apache.maven.wagon:wagon-ssh:${project.property('wagon.version')}" wagon "org.apache.maven.wagon:wagon-ssh:${project.property('wagon.version')}"
} }
sourceCompatibility = JavaVersion.VERSION_1_8 compileJava {
targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:all,-fallthrough" << "-profile" << "compact1" options.compilerArgs << "-Xlint:all,-fallthrough"
} }
clean { clean {

View file

@ -1,7 +1,9 @@
group = org.xbib group = org.xbib
name = z3950 name = z3950
version = 1.1.0 version = 1.2.0
xbib-cql.version = 1.2.0 xbib-cql.version = 1.2.0
netty.version = 4.1.29.Final
junit.version = 4.12 junit.version = 4.12
wagon.version = 3.0.0 wagon.version = 3.0.0

Binary file not shown.

View file

@ -1,6 +1,6 @@
#Thu Feb 08 11:39:35 CET 2018 #Fri Sep 07 18:47:56 CEST 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip

View file

@ -6,4 +6,6 @@ plugins {
dependencies { dependencies {
compile project(':asn1') compile project(':asn1')
compile "org.xbib:cql-common:${project.property('xbib-cql.version')}" compile "org.xbib:cql-common:${project.property('xbib-cql.version')}"
compile "io.netty:netty-handler:${project.property('netty.version')}"
compile "io.netty:netty-transport:${project.property('netty.version')}"
} }

View file

@ -2,7 +2,8 @@ package org.xbib.io.iso23950;
import org.xbib.asn1.ASN1Exception; import org.xbib.asn1.ASN1Exception;
import org.xbib.asn1.ASN1Integer; import org.xbib.asn1.ASN1Integer;
import org.xbib.asn1.BEREncoding; import org.xbib.asn1.io.InputStreamBERReader;
import org.xbib.asn1.io.OutputStreamBERWriter;
import org.xbib.cql.CQLParser; import org.xbib.cql.CQLParser;
import org.xbib.io.iso23950.cql.CQLRPNGenerator; import org.xbib.io.iso23950.cql.CQLRPNGenerator;
import org.xbib.io.iso23950.operations.InitOperation; import org.xbib.io.iso23950.operations.InitOperation;
@ -18,14 +19,17 @@ import org.xbib.io.iso23950.v3.RPNQuery;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader; import java.io.StringReader;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -60,13 +64,19 @@ public class ZClient implements AutoCloseable {
private final List<String> databases; private final List<String> databases;
private final Socket socket; private final Integer preferredMessageSize;
private final BufferedInputStream src; private final InitListener initListener;
private final BufferedOutputStream dest; private final Lock lock;
public ZClient(String host, int port, String user, String pass, long timeout, private Socket socket;
private InputStreamBERReader berReader;
private OutputStreamBERWriter berWriter;
private ZClient(String host, int port, String user, String pass, long timeout,
String preferredRecordSyntax, String preferredRecordSyntax,
String resultSetName, String resultSetName,
String elementSetName, String elementSetName,
@ -75,7 +85,7 @@ public class ZClient implements AutoCloseable {
String type, String type,
List<String> databases, List<String> databases,
Integer preferredMessageSize, Integer preferredMessageSize,
InitListener initListener) throws IOException { InitListener initListener) {
this.host = host; this.host = host;
this.port = port; this.port = port;
this.user = user; this.user = user;
@ -88,135 +98,82 @@ public class ZClient implements AutoCloseable {
this.format = format; this.format = format;
this.type = type; this.type = type;
this.databases = databases; this.databases = databases;
Socket socket = new Socket(); this.preferredMessageSize = preferredMessageSize;
socket.connect(new InetSocketAddress(host, port), (int) timeout); this.initListener = initListener;
socket.setSoTimeout((int) timeout * 1000); this.lock = new ReentrantLock();
this.socket = socket;
this.src = new BufferedInputStream(socket.getInputStream());
this.dest = new BufferedOutputStream(socket.getOutputStream());
// always send init operation after socket init
InitOperation init = new InitOperation();
if (init.execute(this, preferredMessageSize, initListener)) {
throw new IOException("could not initiatie connection");
}
}
public boolean isConnected() {
return socket != null && socket.isConnected();
} }
@Override @Override
public void close() { public void close() {
if (isConnected()) { if (isConnected()) {
try { try {
sendClose(0); lock.lock();
} catch (IOException e) { try {
logger.log(Level.WARNING, "while attempting to send close for close connection: " + e.getMessage(), e); sendClose(0);
} } catch (IOException e) {
try { logger.log(Level.WARNING, "while attempting to send close for close connection: " + e.getMessage(), e);
if (src != null) {
src.close();
} }
} catch (IOException e) { try {
logger.log(Level.WARNING, "error attempting to close src: " + e.getMessage(), e); berReader.close();
} } catch (IOException e) {
try { logger.log(Level.WARNING, "error attempting to close src: " + e.getMessage(), e);
if (dest != null) {
dest.close();
} }
} catch (IOException e) { try {
logger.log(Level.WARNING, "error attempting to close dest: " + e.getMessage(), e); berWriter.close();
} } catch (IOException e) {
try { logger.log(Level.WARNING, "error attempting to close dest: " + e.getMessage(), e);
if (socket != null) {
socket.close();
} }
} catch (IOException e) { try {
logger.log(Level.WARNING, "error attempting to close socket: " + e.getMessage(), e); if (socket != null) {
socket.close();
}
} catch (IOException e) {
logger.log(Level.WARNING, "error attempting to close socket: " + e.getMessage(), e);
}
} finally {
lock.unlock();
} }
} }
} }
/**
* Send a close request to the server.
*
* @param reason reason Reason codes are:
* 0=finished 1=shutdown 2=system problem 3=cost limits
* 4=resources 5=security violation 6=protocol error 7=lack of activity
* 8=peer abort 9=unspecified
* @throws IOException if close fails
*/
public void sendClose(int reason) throws IOException {
PDU pdu = new PDU();
pdu.c_close = new Close();
pdu.c_close.sCloseReason = new CloseReason();
pdu.c_close.sCloseReason.value = new ASN1Integer(reason);
pdu.c_close.sReferenceId = null;
writePDU(pdu);
// do not wait, it may hang
//waitClosePDU();
}
public void writePDU(PDU pdu) throws IOException {
if (dest == null) {
throw new IOException("no output stream");
}
try {
pdu.berEncode().output(dest);
dest.flush();
} catch (ASN1Exception ex) {
throw new IOException(ex);
}
}
public PDU readPDU() throws IOException {
if (src == null) {
throw new IOException("no input");
}
try {
BEREncoding ber = BEREncoding.input(src);
if (ber == null) {
throw new IOException("read PDU error");
}
return new PDU(ber, true);
} catch (ASN1Exception ex) {
throw new IOException(ex);
} catch (NullPointerException ex) {
throw new IOException("connection read PDU error", ex);
}
}
public int executeCQL(String query, int offset, int length, public int executeCQL(String query, int offset, int length,
ResponseListener responseListener, ResponseListener responseListener,
RecordListener recordListener) throws IOException { RecordListener recordListener) throws IOException {
if (query == null) { if (query == null) {
throw new IllegalArgumentException("no query"); throw new IllegalArgumentException("no query");
} }
SearchOperation search = new SearchOperation(); ensureConnected();
boolean success = search.execute(this, createRPNQueryFromCQL(query)); try {
if (!success) { lock.lock();
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query)); SearchOperation searchOperation = new SearchOperation(berReader, berWriter, resultSetName, databases, host);
} else { boolean success = searchOperation.execute(createRPNQueryFromCQL(query));
if (responseListener == null) { if (!success) {
responseListener = (status, total, returned, elapsedMillis) -> { logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]", } else {
elapsedMillis, total, returned, query)); if (responseListener == null) {
}; responseListener = (status, total, returned, elapsedMillis) -> {
} logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
if (search.getCount() > 0) { elapsedMillis, total, returned, query));
PresentOperation present = new PresentOperation(); };
if (offset < 1) {
// Z39.50 present bails out when offset = 0
offset = 1;
} }
if (length > search.getCount()) { if (searchOperation.getCount() > 0) {
// avoid condition 13 "Present request out-of-range" PresentOperation present = new PresentOperation(berReader, berWriter,
length = search.getCount(); resultSetName, elementSetName, preferredRecordSyntax);
if (offset < 1) {
// Z39.50 present bails out when offset = 0
offset = 1;
}
if (length > searchOperation.getCount()) {
// avoid condition 13 "Present request out-of-range"
length = searchOperation.getCount();
}
present.execute(offset, length, searchOperation.getCount(), responseListener, recordListener);
} }
present.execute(this, offset, length, search.getCount(), responseListener, recordListener);
} }
return searchOperation.getCount();
} finally {
lock.unlock();
} }
return search.getCount();
} }
public int executePQF(String query, int offset, int length, public int executePQF(String query, int offset, int length,
@ -225,32 +182,39 @@ public class ZClient implements AutoCloseable {
if (query == null) { if (query == null) {
throw new IllegalArgumentException("no query"); throw new IllegalArgumentException("no query");
} }
SearchOperation search = new SearchOperation(); ensureConnected();
search.execute(this, createRPNQueryFromPQF(query)); try {
if (!search.isSuccess()) { lock.lock();
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query)); SearchOperation search = new SearchOperation(berReader, berWriter, resultSetName, databases, host);
} else { search.execute(createRPNQueryFromPQF(query));
if (responseListener == null) { if (!search.isSuccess()) {
responseListener = (status, total, returned, elapsedMillis) -> { logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]", } else {
elapsedMillis, total, returned, query)); if (responseListener == null) {
}; responseListener = (status, total, returned, elapsedMillis) -> {
} logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
if (search.getCount() > 0) { elapsedMillis, total, returned, query));
logger.log(Level.INFO, "search returned " + search.getCount()); };
PresentOperation present = new PresentOperation();
if (offset < 1) {
// Z39.50 bails out when offset = 0
offset = 1;
} }
if (length > search.getCount()) { if (search.getCount() > 0) {
// avoid condition 13 "Present request out-of-range" logger.log(Level.INFO, "search returned " + search.getCount());
length = search.getCount(); PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax);
if (offset < 1) {
// Z39.50 bails out when offset = 0
offset = 1;
}
if (length > search.getCount()) {
// avoid condition 13 "Present request out-of-range"
length = search.getCount();
}
present.execute(offset, length, search.getCount(), responseListener, recordListener);
} }
present.execute(this, offset, length, search.getCount(), responseListener, recordListener);
} }
return search.getCount();
} finally {
lock.unlock();
} }
return search.getCount();
} }
public String getHost() { public String getHost() {
@ -301,7 +265,7 @@ public class ZClient implements AutoCloseable {
return databases; return databases;
} }
public RPNQuery createRPNQueryFromCQL(String query) throws IOException { private RPNQuery createRPNQueryFromCQL(String query) {
CQLRPNGenerator generator = new CQLRPNGenerator(); CQLRPNGenerator generator = new CQLRPNGenerator();
CQLParser parser = new CQLParser(query); CQLParser parser = new CQLParser(query);
parser.parse(); parser.parse();
@ -310,7 +274,7 @@ public class ZClient implements AutoCloseable {
} }
public RPNQuery createRPNQueryFromPQF(String query) throws IOException { private RPNQuery createRPNQueryFromPQF(String query) {
PQFRPNGenerator generator = new PQFRPNGenerator(); PQFRPNGenerator generator = new PQFRPNGenerator();
PQFParser parser = new PQFParser(new StringReader(query)); PQFParser parser = new PQFParser(new StringReader(query));
parser.parse(); parser.parse();
@ -318,6 +282,69 @@ public class ZClient implements AutoCloseable {
return generator.getResult(); return generator.getResult();
} }
/**
* Send a close request to the server.
*
* @param reason reason Reason codes are:
* 0=finished 1=shutdown 2=system problem 3=cost limits
* 4=resources 5=security violation 6=protocol error 7=lack of activity
* 8=peer abort 9=unspecified
* @throws IOException if close fails
*/
private void sendClose(int reason) throws IOException {
if (!isConnected()) {
return;
}
try {
lock.lock();
PDU pdu = new PDU();
pdu.c_close = new Close();
pdu.c_close.sCloseReason = new CloseReason();
pdu.c_close.sCloseReason.value = new ASN1Integer(reason);
pdu.c_close.sReferenceId = null;
try {
berWriter.write(pdu.berEncode());
} catch (ASN1Exception ex) {
throw new IOException(ex);
}
// do not wait, it may hang
//waitClosePDU();
} finally {
lock.unlock();
}
}
private void connect() throws IOException {
try {
lock.lock();
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), (int) timeout);
socket.setSoTimeout((int) timeout * 1000);
this.socket = socket;
InputStream src = new BufferedInputStream(socket.getInputStream());
OutputStream dest = new BufferedOutputStream(socket.getOutputStream());
this.berReader = new InputStreamBERReader(src);
this.berWriter = new OutputStreamBERWriter(dest);
InitOperation initOperation = new InitOperation(berReader, berWriter, user, pass);
if (initOperation.execute(preferredMessageSize, initListener)) {
throw new IOException("could not initiate connection");
}
} finally {
lock.unlock();
}
}
private boolean isConnected() {
return socket != null && socket.isConnected();
}
private void ensureConnected() throws IOException {
if (!isConnected()) {
connect();
}
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }
@ -432,20 +459,16 @@ public class ZClient implements AutoCloseable {
} }
public ZClient build() { public ZClient build() {
try { return new ZClient(host, port, user, pass, timeout,
return new ZClient(host, port, user, pass, timeout, preferredRecordSyntax,
preferredRecordSyntax, resultSetName,
resultSetName, elementSetName,
elementSetName, encoding,
encoding, format,
format, type,
type, databases,
databases, preferredMessageSize,
preferredMessageSize, initListener);
initListener);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} }
} }
} }

View file

@ -0,0 +1,69 @@
package org.xbib.io.iso23950.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
import org.xbib.io.iso23950.v3.PDU;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Client {
private final EventLoopGroup group;
private final Bootstrap clientBootstrap;
public Client() {
this.group = new NioEventLoopGroup();
clientBootstrap = new Bootstrap();
clientBootstrap.group(group);
clientBootstrap.channel(NioSocketChannel.class);
clientBootstrap.remoteAddress(new InetSocketAddress("localhost", 9999));
clientBootstrap.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new Handler());
}
});
}
public void connect() throws InterruptedException {
ChannelFuture channelFuture = clientBootstrap.connect().sync();
channelFuture.channel().closeFuture().sync();
}
public void writePDU(PDU pdu) throws IOException {
}
public void shutdown() throws InterruptedException {
group.shutdownGracefully().sync();
}
class Handler extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext channelHandlerContext){
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("Netty Rocks!", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause){
cause.printStackTrace();
channelHandlerContext.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
}
}
}

View file

@ -0,0 +1,44 @@
package org.xbib.io.iso23950.operations;
import org.xbib.asn1.ASN1Exception;
import org.xbib.asn1.BEREncoding;
import org.xbib.asn1.io.BERReader;
import org.xbib.asn1.io.BERWriter;
import org.xbib.io.iso23950.v3.PDU;
import java.io.IOException;
public class AbstractOperation {
protected final BERReader reader;
protected final BERWriter writer;
AbstractOperation(BERReader reader, BERWriter writer) {
this.reader = reader;
this.writer = writer;
}
protected void writePDU(PDU pdu) throws IOException {
try {
writer.write(pdu.berEncode());
} catch (ASN1Exception ex) {
throw new IOException(ex);
}
}
protected PDU readPDU() throws IOException {
try {
BEREncoding ber = reader.read();
if (ber == null) {
throw new IOException("read PDU error");
}
return new PDU(ber, true);
} catch (ASN1Exception ex) {
throw new IOException(ex);
} catch (NullPointerException ex) {
throw new IOException("connection read PDU error", ex);
}
}
}

View file

@ -3,8 +3,9 @@ package org.xbib.io.iso23950.operations;
import org.xbib.asn1.ASN1BitString; import org.xbib.asn1.ASN1BitString;
import org.xbib.asn1.ASN1GeneralString; import org.xbib.asn1.ASN1GeneralString;
import org.xbib.asn1.ASN1Integer; import org.xbib.asn1.ASN1Integer;
import org.xbib.asn1.io.BERReader;
import org.xbib.asn1.io.BERWriter;
import org.xbib.io.iso23950.InitListener; import org.xbib.io.iso23950.InitListener;
import org.xbib.io.iso23950.ZClient;
import org.xbib.io.iso23950.v3.IdAuthentication; import org.xbib.io.iso23950.v3.IdAuthentication;
import org.xbib.io.iso23950.v3.IdAuthenticationIdPass; import org.xbib.io.iso23950.v3.IdAuthenticationIdPass;
import org.xbib.io.iso23950.v3.InitializeRequest; import org.xbib.io.iso23950.v3.InitializeRequest;
@ -19,9 +20,19 @@ import java.io.IOException;
/** /**
* A Z39.50 Init operation. * A Z39.50 Init operation.
*/ */
public class InitOperation { public class InitOperation extends AbstractOperation {
public boolean execute(ZClient client, Integer preferredMessageSize, private final String user;
private final String pass;
public InitOperation(BERReader reader, BERWriter writer, String user, String pass) {
super(reader, writer);
this.user = user;
this.pass = pass;
}
public boolean execute(Integer preferredMessageSize,
InitListener initListener) throws IOException { InitListener initListener) throws IOException {
InitializeRequest init = new InitializeRequest(); InitializeRequest init = new InitializeRequest();
boolean[] version = new boolean[3]; boolean[] version = new boolean[3];
@ -56,14 +67,14 @@ public class InitOperation {
init.s_implementationName.value = new ASN1GeneralString("Java ZClient"); init.s_implementationName.value = new ASN1GeneralString("Java ZClient");
init.s_implementationVersion = new InternationalString(); init.s_implementationVersion = new InternationalString();
init.s_implementationVersion.value = new ASN1GeneralString("1.00"); init.s_implementationVersion.value = new ASN1GeneralString("1.00");
if (client.getUser() != null) { if (user != null) {
init.s_idAuthentication = new IdAuthentication(); init.s_idAuthentication = new IdAuthentication();
init.s_idAuthentication.c_idPass = new IdAuthenticationIdPass(); init.s_idAuthentication.c_idPass = new IdAuthenticationIdPass();
init.s_idAuthentication.c_idPass.s_userId = new InternationalString(); init.s_idAuthentication.c_idPass.s_userId = new InternationalString();
init.s_idAuthentication.c_idPass.s_userId.value = new ASN1GeneralString(client.getUser()); init.s_idAuthentication.c_idPass.s_userId.value = new ASN1GeneralString(user);
if (client.getPass() != null) { if (pass != null) {
init.s_idAuthentication.c_idPass.s_password = new InternationalString(); init.s_idAuthentication.c_idPass.s_password = new InternationalString();
init.s_idAuthentication.c_idPass.s_password.value = new ASN1GeneralString(client.getPass()); init.s_idAuthentication.c_idPass.s_password.value = new ASN1GeneralString(pass);
} }
/*if (group != null) { /*if (group != null) {
init.s_idAuthentication.c_idPass.s_groupId = new InternationalString(); init.s_idAuthentication.c_idPass.s_groupId = new InternationalString();
@ -72,8 +83,8 @@ public class InitOperation {
} }
PDU pduOut = new PDU(); PDU pduOut = new PDU();
pduOut.c_initRequest = init; pduOut.c_initRequest = init;
client.writePDU(pduOut); writePDU(pduOut);
PDU pduIn = client.readPDU(); PDU pduIn = readPDU();
InitializeResponse initResp = pduIn.c_initResponse; InitializeResponse initResp = pduIn.c_initResponse;
String targetInfo; String targetInfo;
if (initResp.s_implementationName != null) { if (initResp.s_implementationName != null) {

View file

@ -5,11 +5,12 @@ import org.xbib.asn1.ASN1External;
import org.xbib.asn1.ASN1GeneralString; import org.xbib.asn1.ASN1GeneralString;
import org.xbib.asn1.ASN1Integer; import org.xbib.asn1.ASN1Integer;
import org.xbib.asn1.ASN1ObjectIdentifier; import org.xbib.asn1.ASN1ObjectIdentifier;
import org.xbib.asn1.io.BERReader;
import org.xbib.asn1.io.BERWriter;
import org.xbib.io.iso23950.ErrorRecord; import org.xbib.io.iso23950.ErrorRecord;
import org.xbib.io.iso23950.Record; import org.xbib.io.iso23950.Record;
import org.xbib.io.iso23950.RecordListener; import org.xbib.io.iso23950.RecordListener;
import org.xbib.io.iso23950.ResponseListener; import org.xbib.io.iso23950.ResponseListener;
import org.xbib.io.iso23950.ZClient;
import org.xbib.io.iso23950.exceptions.MessageSizeTooSmallException; import org.xbib.io.iso23950.exceptions.MessageSizeTooSmallException;
import org.xbib.io.iso23950.exceptions.NoRecordsReturnedException; import org.xbib.io.iso23950.exceptions.NoRecordsReturnedException;
import org.xbib.io.iso23950.exceptions.RequestTerminatedByAccessControlException; import org.xbib.io.iso23950.exceptions.RequestTerminatedByAccessControlException;
@ -30,13 +31,26 @@ import java.io.IOException;
/** /**
* Present operation for Z39.50. * Present operation for Z39.50.
*/ */
public class PresentOperation { public class PresentOperation extends AbstractOperation {
public void execute(ZClient client, int offset, int length, int total, private final String resultSetName;
private final String elementSetName;
private final String preferredRecordSyntax;
public PresentOperation(BERReader reader, BERWriter writer,
String resultSetName,
String elementSetName,
String preferredRecordSyntax) {
super(reader, writer);
this.resultSetName = resultSetName;
this.elementSetName = elementSetName;
this.preferredRecordSyntax = preferredRecordSyntax;
}
public void execute(int offset, int length, int total,
ResponseListener responseListener, RecordListener recordListener) throws IOException { ResponseListener responseListener, RecordListener recordListener) throws IOException {
String resultSetName = client.getResultSetName();
String elementSetName = client.getElementSetName();
String preferredRecordSyntax = client.getPreferredRecordSyntax();
PresentRequest pr = new PresentRequest(); PresentRequest pr = new PresentRequest();
pr.s_resultSetId = new ResultSetId(); pr.s_resultSetId = new ResultSetId();
pr.s_resultSetId.value = new InternationalString(); pr.s_resultSetId.value = new InternationalString();
@ -51,8 +65,8 @@ public class PresentOperation {
PDU pdu = new PDU(); PDU pdu = new PDU();
pdu.c_presentRequest = pr; pdu.c_presentRequest = pr;
long millis = System.currentTimeMillis(); long millis = System.currentTimeMillis();
client.writePDU(pdu); writePDU(pdu);
pdu = client.readPDU(); pdu = readPDU();
PresentResponse response = pdu.c_presentResponse; PresentResponse response = pdu.c_presentResponse;
int nReturned = response.s_numberOfRecordsReturned != null ? response.s_numberOfRecordsReturned.get() : 0; int nReturned = response.s_numberOfRecordsReturned != null ? response.s_numberOfRecordsReturned.get() : 0;
int status = response.s_presentStatus.value != null ? response.s_presentStatus.value.get() : 0; int status = response.s_presentStatus.value != null ? response.s_presentStatus.value.get() : 0;

View file

@ -6,7 +6,8 @@ import org.xbib.asn1.ASN1Exception;
import org.xbib.asn1.ASN1GeneralString; import org.xbib.asn1.ASN1GeneralString;
import org.xbib.asn1.ASN1Integer; import org.xbib.asn1.ASN1Integer;
import org.xbib.asn1.ASN1Sequence; import org.xbib.asn1.ASN1Sequence;
import org.xbib.io.iso23950.ZClient; import org.xbib.asn1.io.BERReader;
import org.xbib.asn1.io.BERWriter;
import org.xbib.io.iso23950.v3.DatabaseName; import org.xbib.io.iso23950.v3.DatabaseName;
import org.xbib.io.iso23950.v3.InternationalString; import org.xbib.io.iso23950.v3.InternationalString;
import org.xbib.io.iso23950.v3.OtherInformation1; import org.xbib.io.iso23950.v3.OtherInformation1;
@ -26,15 +27,32 @@ import java.util.Map;
/** /**
* Base class for Z39.50 Search operation. * Base class for Z39.50 Search operation.
*/ */
public class SearchOperation { public class SearchOperation extends AbstractOperation {
private int count = -1; private int count = -1;
private boolean status = false; private boolean status = false;
private Map<ASN1Any, Integer> results = new HashMap<>(); private final Map<ASN1Any, Integer> results;
public boolean execute(ZClient client, RPNQuery rpn) throws IOException { private final String resultSetName;
private final List<String> databases;
private final String host;
public SearchOperation(BERReader reader, BERWriter writer,
String resultSetName,
List<String> databases,
String host) {
super(reader, writer);
this.resultSetName = resultSetName;
this.databases = databases;
this.host = host;
this.results = new HashMap<>();
}
public boolean execute(RPNQuery rpn) throws IOException {
try { try {
SearchRequest search = new SearchRequest(); SearchRequest search = new SearchRequest();
search.s_query = new Query(); search.s_query = new Query();
@ -44,8 +62,7 @@ public class SearchOperation {
search.s_mediumSetPresentNumber = new ASN1Integer(0); search.s_mediumSetPresentNumber = new ASN1Integer(0);
search.s_replaceIndicator = new ASN1Boolean(true); search.s_replaceIndicator = new ASN1Boolean(true);
search.s_resultSetName = new InternationalString(); search.s_resultSetName = new InternationalString();
search.s_resultSetName.value = new ASN1GeneralString(client.getResultSetName()); search.s_resultSetName.value = new ASN1GeneralString(resultSetName);
List<String> databases = client.getDatabases();
DatabaseName dbs[] = new DatabaseName[databases.size()]; DatabaseName dbs[] = new DatabaseName[databases.size()];
for (int n = 0; n < databases.size(); n++) { for (int n = 0; n < databases.size(); n++) {
dbs[n] = new DatabaseName(); dbs[n] = new DatabaseName();
@ -55,8 +72,8 @@ public class SearchOperation {
search.s_databaseNames = dbs; search.s_databaseNames = dbs;
PDU pduRequest = new PDU(); PDU pduRequest = new PDU();
pduRequest.c_searchRequest = search; pduRequest.c_searchRequest = search;
client.writePDU(pduRequest); writePDU(pduRequest);
PDU pduResponse = client.readPDU(); PDU pduResponse = readPDU();
SearchResponse response = pduResponse.c_searchResponse; SearchResponse response = pduResponse.c_searchResponse;
count = response.s_resultCount.get(); count = response.s_resultCount.get();
ASN1Boolean b = response.s_searchStatus; ASN1Boolean b = response.s_searchStatus;
@ -71,7 +88,7 @@ public class SearchOperation {
// //
} }
} }
throw new IOException(client.getHost() + ": " + message); throw new IOException(host + ": " + message);
} }
PresentStatus presentStatus = response.s_presentStatus; PresentStatus presentStatus = response.s_presentStatus;
if (presentStatus != null && presentStatus.value != null && presentStatus.value.get() == 5) { if (presentStatus != null && presentStatus.value != null && presentStatus.value.get() == 5) {
@ -90,7 +107,7 @@ public class SearchOperation {
if (!dbName.value.value.get().equalsIgnoreCase(databases.get(i))) { if (!dbName.value.value.get().equalsIgnoreCase(databases.get(i))) {
String message = "database name listed in additional search info " + String message = "database name listed in additional search info " +
"doesn't match database name in names set."; "doesn't match database name in names set.";
throw new IOException(client.getHost() + ": " + message); throw new IOException(host + ": " + message);
} }
ASN1Integer res = (ASN1Integer) details[1]; ASN1Integer res = (ASN1Integer) details[1];
results.put(target, res.get()); results.put(target, res.get());
@ -101,7 +118,7 @@ public class SearchOperation {
} }
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
throw new IOException(client.getHost() + ": timeout", e); throw new IOException(host + ": timeout", e);
} }
return status; return status;
} }

View file

@ -56,7 +56,6 @@ public class ZClientTest {
} }
} }
private static ZClient newZClient(String name) throws IOException { private static ZClient newZClient(String name) throws IOException {
return newZClient(getProperties(name)); return newZClient(getProperties(name));
} }
@ -69,6 +68,7 @@ public class ZClientTest {
} }
return properties; return properties;
} }
private static ZClient newZClient(Properties properties) { private static ZClient newZClient(Properties properties) {
ZClient.Builder builder = ZClient.builder(); ZClient.Builder builder = ZClient.builder();
if (properties.containsKey("host")) { if (properties.containsKey("host")) {