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) {
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
if (encoding.length < 1) {
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) {
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
if (encoding.length != 1) {
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");
}
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
if (encoding.length < 1) {
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");
}
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
if (encoding.length < 1) {
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");
}
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
if (encoding.length < 2) {
throw new ASN1EncodingException("invalid encoding, length = " +
encoding.length);

View file

@ -75,7 +75,7 @@ public class ASN1OctetString extends ASN1Any {
}
if (berEncoding instanceof BERPrimitive) {
BERPrimitive ber = (BERPrimitive) berEncoding;
int[] encoding = ber.peek();
int[] encoding = ber.getContentOctets();
StringBuilder buf = new StringBuilder(encoding.length);
for (int anEncoding : encoding) {
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.
* @throws ASN1Exception If the BER encoding is incorrect.
*/
public ASN1VideotexString(BEREncoding ber, boolean checkTag)
throws ASN1Exception {
public ASN1VideotexString(BEREncoding ber, boolean checkTag) throws ASN1Exception {
super(ber, false);
if (checkTag && (ber.tagGet() != VIDEOTEX_STRING_TAG || ber.tagTypeGet() != BEREncoding.UNIVERSAL_TAG)) {
throw new ASN1EncodingException("bad BER: tag=" + ber.tagGet() +

View file

@ -1,8 +1,5 @@
package org.xbib.asn1;
import java.io.IOException;
import java.io.OutputStream;
/**
* BERConstructed.
* This class represents a BER encoded ASN.1 object which is
@ -38,21 +35,8 @@ public class BERConstructed extends BEREncoding {
contentElements = elements;
}
/**
* 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.
*
* @param dest OutputStream to write encoding to.
*/
@Override
public void output(OutputStream dest) throws IOException {
outputHead(dest);
for (BEREncoding contentElement : contentElements) {
contentElement.output(dest);
}
public BEREncoding[] getContentElements() {
return contentElements;
}
/**

View file

@ -1,11 +1,5 @@
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 is an abstract base class from which there are two specific
@ -24,8 +18,6 @@ import java.util.List;
*/
public abstract class BEREncoding {
private static final String ERROR = "Unexpected end in BER encoding";
/**
* Constant for indicating UNIVERSAL tag type. The value matches
* 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;
private static final int MAX_BER_SIZE = 65536 * 4;
/**
* The tag type of this BER encoded object. This value must be
* the same as that encoded in the identiferEncoding.
@ -86,153 +77,25 @@ public abstract class BEREncoding {
*/
private int[] lengthEncoding;
/**
* The public wrapping for doInput() method.
*
* @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[] getIdentifierEncoding() {
return identifierEncoding;
}
/**
* 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.
* @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[] getLengthEncoding() {
return lengthEncoding;
}
/**
* 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.
*
* @param dest - the OutputStream to write the encoding to.
* @throws IOException On output I/O error
*/
public abstract void output(OutputStream dest) throws IOException;
public int getITag() {
return iTag;
}
public int getITagType() {
return iTagType;
}
public int getITotalLength() {
return iTotalLength;
}
/**
* 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;
}
/*
* 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
* position offset) with the encoding for the identifier and length.

View file

@ -1,8 +1,5 @@
package org.xbib.asn1;
import java.io.IOException;
import java.io.OutputStream;
/**
* This class represents a primitive ASN.1 object encoded
* 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#PRIVATE_TAG
*/
BERPrimitive(int asn1Class, int tag, int[] contents)
throws ASN1Exception {
public BERPrimitive(int asn1Class, int tag, int[] contents) throws ASN1Exception {
init(asn1Class, false, tag, contents.length);
contentsOctets = contents;
}
@ -49,24 +45,10 @@ public class BERPrimitive extends BEREncoding {
* This method allows the content octets to be examined.
* Once again, only the ASN.1 standard objects should be using this.
*/
int[] peek() {
public int[] getContentOctets() {
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
* 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')}"
}
compileJava {
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) {
options.compilerArgs << "-Xlint:all,-fallthrough" << "-profile" << "compact1"
options.compilerArgs << "-Xlint:all,-fallthrough"
}
clean {

View file

@ -1,7 +1,9 @@
group = org.xbib
name = z3950
version = 1.1.0
version = 1.2.0
xbib-cql.version = 1.2.0
netty.version = 4.1.29.Final
junit.version = 4.12
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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 {
compile project(':asn1')
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.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.io.iso23950.cql.CQLRPNGenerator;
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.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
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.Logger;
@ -60,13 +64,19 @@ public class ZClient implements AutoCloseable {
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 resultSetName,
String elementSetName,
@ -75,7 +85,7 @@ public class ZClient implements AutoCloseable {
String type,
List<String> databases,
Integer preferredMessageSize,
InitListener initListener) throws IOException {
InitListener initListener) {
this.host = host;
this.port = port;
this.user = user;
@ -88,42 +98,28 @@ public class ZClient implements AutoCloseable {
this.format = format;
this.type = type;
this.databases = databases;
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), (int) timeout);
socket.setSoTimeout((int) timeout * 1000);
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();
this.preferredMessageSize = preferredMessageSize;
this.initListener = initListener;
this.lock = new ReentrantLock();
}
@Override
public void close() {
if (isConnected()) {
try {
lock.lock();
try {
sendClose(0);
} catch (IOException e) {
logger.log(Level.WARNING, "while attempting to send close for close connection: " + e.getMessage(), e);
}
try {
if (src != null) {
src.close();
}
berReader.close();
} catch (IOException e) {
logger.log(Level.WARNING, "error attempting to close src: " + e.getMessage(), e);
}
try {
if (dest != null) {
dest.close();
}
berWriter.close();
} catch (IOException e) {
logger.log(Level.WARNING, "error attempting to close dest: " + e.getMessage(), e);
}
@ -134,56 +130,10 @@ public class ZClient implements AutoCloseable {
} 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,
@ -192,8 +142,11 @@ public class ZClient implements AutoCloseable {
if (query == null) {
throw new IllegalArgumentException("no query");
}
SearchOperation search = new SearchOperation();
boolean success = search.execute(this, createRPNQueryFromCQL(query));
ensureConnected();
try {
lock.lock();
SearchOperation searchOperation = new SearchOperation(berReader, berWriter, resultSetName, databases, host);
boolean success = searchOperation.execute(createRPNQueryFromCQL(query));
if (!success) {
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
} else {
@ -203,20 +156,24 @@ public class ZClient implements AutoCloseable {
elapsedMillis, total, returned, query));
};
}
if (search.getCount() > 0) {
PresentOperation present = new PresentOperation();
if (searchOperation.getCount() > 0) {
PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax);
if (offset < 1) {
// Z39.50 present bails out when offset = 0
offset = 1;
}
if (length > search.getCount()) {
if (length > searchOperation.getCount()) {
// avoid condition 13 "Present request out-of-range"
length = search.getCount();
length = searchOperation.getCount();
}
present.execute(this, offset, length, search.getCount(), responseListener, recordListener);
present.execute(offset, length, searchOperation.getCount(), responseListener, recordListener);
}
}
return search.getCount();
return searchOperation.getCount();
} finally {
lock.unlock();
}
}
public int executePQF(String query, int offset, int length,
@ -225,8 +182,11 @@ public class ZClient implements AutoCloseable {
if (query == null) {
throw new IllegalArgumentException("no query");
}
SearchOperation search = new SearchOperation();
search.execute(this, createRPNQueryFromPQF(query));
ensureConnected();
try {
lock.lock();
SearchOperation search = new SearchOperation(berReader, berWriter, resultSetName, databases, host);
search.execute(createRPNQueryFromPQF(query));
if (!search.isSuccess()) {
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
} else {
@ -238,7 +198,8 @@ public class ZClient implements AutoCloseable {
}
if (search.getCount() > 0) {
logger.log(Level.INFO, "search returned " + search.getCount());
PresentOperation present = new PresentOperation();
PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax);
if (offset < 1) {
// Z39.50 bails out when offset = 0
offset = 1;
@ -247,10 +208,13 @@ public class ZClient implements AutoCloseable {
// avoid condition 13 "Present request out-of-range"
length = search.getCount();
}
present.execute(this, offset, length, search.getCount(), responseListener, recordListener);
present.execute(offset, length, search.getCount(), responseListener, recordListener);
}
}
return search.getCount();
} finally {
lock.unlock();
}
}
public String getHost() {
@ -301,7 +265,7 @@ public class ZClient implements AutoCloseable {
return databases;
}
public RPNQuery createRPNQueryFromCQL(String query) throws IOException {
private RPNQuery createRPNQueryFromCQL(String query) {
CQLRPNGenerator generator = new CQLRPNGenerator();
CQLParser parser = new CQLParser(query);
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();
PQFParser parser = new PQFParser(new StringReader(query));
parser.parse();
@ -318,6 +282,69 @@ public class ZClient implements AutoCloseable {
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() {
return new Builder();
}
@ -432,7 +459,6 @@ public class ZClient implements AutoCloseable {
}
public ZClient build() {
try {
return new ZClient(host, port, user, pass, timeout,
preferredRecordSyntax,
resultSetName,
@ -443,9 +469,6 @@ public class ZClient implements AutoCloseable {
databases,
preferredMessageSize,
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.ASN1GeneralString;
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.ZClient;
import org.xbib.io.iso23950.v3.IdAuthentication;
import org.xbib.io.iso23950.v3.IdAuthenticationIdPass;
import org.xbib.io.iso23950.v3.InitializeRequest;
@ -19,9 +20,19 @@ import java.io.IOException;
/**
* 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 {
InitializeRequest init = new InitializeRequest();
boolean[] version = new boolean[3];
@ -56,14 +67,14 @@ public class InitOperation {
init.s_implementationName.value = new ASN1GeneralString("Java ZClient");
init.s_implementationVersion = new InternationalString();
init.s_implementationVersion.value = new ASN1GeneralString("1.00");
if (client.getUser() != null) {
if (user != null) {
init.s_idAuthentication = new IdAuthentication();
init.s_idAuthentication.c_idPass = new IdAuthenticationIdPass();
init.s_idAuthentication.c_idPass.s_userId = new InternationalString();
init.s_idAuthentication.c_idPass.s_userId.value = new ASN1GeneralString(client.getUser());
if (client.getPass() != null) {
init.s_idAuthentication.c_idPass.s_userId.value = new ASN1GeneralString(user);
if (pass != null) {
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) {
init.s_idAuthentication.c_idPass.s_groupId = new InternationalString();
@ -72,8 +83,8 @@ public class InitOperation {
}
PDU pduOut = new PDU();
pduOut.c_initRequest = init;
client.writePDU(pduOut);
PDU pduIn = client.readPDU();
writePDU(pduOut);
PDU pduIn = readPDU();
InitializeResponse initResp = pduIn.c_initResponse;
String targetInfo;
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.ASN1Integer;
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.Record;
import org.xbib.io.iso23950.RecordListener;
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.NoRecordsReturnedException;
import org.xbib.io.iso23950.exceptions.RequestTerminatedByAccessControlException;
@ -30,13 +31,26 @@ import java.io.IOException;
/**
* 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 {
String resultSetName = client.getResultSetName();
String elementSetName = client.getElementSetName();
String preferredRecordSyntax = client.getPreferredRecordSyntax();
PresentRequest pr = new PresentRequest();
pr.s_resultSetId = new ResultSetId();
pr.s_resultSetId.value = new InternationalString();
@ -51,8 +65,8 @@ public class PresentOperation {
PDU pdu = new PDU();
pdu.c_presentRequest = pr;
long millis = System.currentTimeMillis();
client.writePDU(pdu);
pdu = client.readPDU();
writePDU(pdu);
pdu = readPDU();
PresentResponse response = pdu.c_presentResponse;
int nReturned = response.s_numberOfRecordsReturned != null ? response.s_numberOfRecordsReturned.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.ASN1Integer;
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.InternationalString;
import org.xbib.io.iso23950.v3.OtherInformation1;
@ -26,15 +27,32 @@ import java.util.Map;
/**
* Base class for Z39.50 Search operation.
*/
public class SearchOperation {
public class SearchOperation extends AbstractOperation {
private int count = -1;
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 {
SearchRequest search = new SearchRequest();
search.s_query = new Query();
@ -44,8 +62,7 @@ public class SearchOperation {
search.s_mediumSetPresentNumber = new ASN1Integer(0);
search.s_replaceIndicator = new ASN1Boolean(true);
search.s_resultSetName = new InternationalString();
search.s_resultSetName.value = new ASN1GeneralString(client.getResultSetName());
List<String> databases = client.getDatabases();
search.s_resultSetName.value = new ASN1GeneralString(resultSetName);
DatabaseName dbs[] = new DatabaseName[databases.size()];
for (int n = 0; n < databases.size(); n++) {
dbs[n] = new DatabaseName();
@ -55,8 +72,8 @@ public class SearchOperation {
search.s_databaseNames = dbs;
PDU pduRequest = new PDU();
pduRequest.c_searchRequest = search;
client.writePDU(pduRequest);
PDU pduResponse = client.readPDU();
writePDU(pduRequest);
PDU pduResponse = readPDU();
SearchResponse response = pduResponse.c_searchResponse;
count = response.s_resultCount.get();
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;
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))) {
String message = "database name listed in additional search info " +
"doesn't match database name in names set.";
throw new IOException(client.getHost() + ": " + message);
throw new IOException(host + ": " + message);
}
ASN1Integer res = (ASN1Integer) details[1];
results.put(target, res.get());
@ -101,7 +118,7 @@ public class SearchOperation {
}
}
} catch (SocketTimeoutException e) {
throw new IOException(client.getHost() + ": timeout", e);
throw new IOException(host + ": timeout", e);
}
return status;
}

View file

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