refactor to BERReader/BERWriter interface, add locking
This commit is contained in:
parent
e9b3b4fa5a
commit
00412e7577
26 changed files with 687 additions and 406 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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() +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
12
asn1/src/main/java/org/xbib/asn1/io/BERReader.java
Normal file
12
asn1/src/main/java/org/xbib/asn1/io/BERReader.java
Normal 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;
|
||||
}
|
12
asn1/src/main/java/org/xbib/asn1/io/BERWriter.java
Normal file
12
asn1/src/main/java/org/xbib/asn1/io/BERWriter.java
Normal 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;
|
||||
}
|
166
asn1/src/main/java/org/xbib/asn1/io/InputStreamBERReader.java
Normal file
166
asn1/src/main/java/org/xbib/asn1/io/InputStreamBERReader.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
14
build.gradle
14
build.gradle
|
@ -39,12 +39,18 @@ subprojects {
|
|||
wagon "org.apache.maven.wagon:wagon-ssh:${project.property('wagon.version')}"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
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 {
|
||||
|
|
|
@ -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
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,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
|
||||
|
|
|
@ -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')}"
|
||||
}
|
||||
|
|
|
@ -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,135 +98,82 @@ 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 {
|
||||
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();
|
||||
lock.lock();
|
||||
try {
|
||||
sendClose(0);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "while attempting to send close for close connection: " + e.getMessage(), e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "error attempting to close src: " + e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
if (dest != null) {
|
||||
dest.close();
|
||||
try {
|
||||
berReader.close();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "error attempting to close src: " + e.getMessage(), e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "error attempting to close dest: " + e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
try {
|
||||
berWriter.close();
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "error attempting to close dest: " + e.getMessage(), e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "error attempting to close socket: " + e.getMessage(), e);
|
||||
try {
|
||||
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,
|
||||
ResponseListener responseListener,
|
||||
RecordListener recordListener) throws IOException {
|
||||
if (query == null) {
|
||||
throw new IllegalArgumentException("no query");
|
||||
}
|
||||
SearchOperation search = new SearchOperation();
|
||||
boolean success = search.execute(this, createRPNQueryFromCQL(query));
|
||||
if (!success) {
|
||||
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
|
||||
} else {
|
||||
if (responseListener == null) {
|
||||
responseListener = (status, total, returned, elapsedMillis) -> {
|
||||
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
|
||||
elapsedMillis, total, returned, query));
|
||||
};
|
||||
}
|
||||
if (search.getCount() > 0) {
|
||||
PresentOperation present = new PresentOperation();
|
||||
if (offset < 1) {
|
||||
// Z39.50 present bails out when offset = 0
|
||||
offset = 1;
|
||||
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 {
|
||||
if (responseListener == null) {
|
||||
responseListener = (status, total, returned, elapsedMillis) -> {
|
||||
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
|
||||
elapsedMillis, total, returned, query));
|
||||
};
|
||||
}
|
||||
if (length > search.getCount()) {
|
||||
// avoid condition 13 "Present request out-of-range"
|
||||
length = search.getCount();
|
||||
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 > 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,
|
||||
|
@ -225,32 +182,39 @@ public class ZClient implements AutoCloseable {
|
|||
if (query == null) {
|
||||
throw new IllegalArgumentException("no query");
|
||||
}
|
||||
SearchOperation search = new SearchOperation();
|
||||
search.execute(this, createRPNQueryFromPQF(query));
|
||||
if (!search.isSuccess()) {
|
||||
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
|
||||
} else {
|
||||
if (responseListener == null) {
|
||||
responseListener = (status, total, returned, elapsedMillis) -> {
|
||||
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
|
||||
elapsedMillis, total, returned, query));
|
||||
};
|
||||
}
|
||||
if (search.getCount() > 0) {
|
||||
logger.log(Level.INFO, "search returned " + search.getCount());
|
||||
PresentOperation present = new PresentOperation();
|
||||
if (offset < 1) {
|
||||
// Z39.50 bails out when offset = 0
|
||||
offset = 1;
|
||||
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 {
|
||||
if (responseListener == null) {
|
||||
responseListener = (status, total, returned, elapsedMillis) -> {
|
||||
logger.log(Level.INFO, MessageFormat.format("[{0}ms] [{1}] [{2}] [{3}]",
|
||||
elapsedMillis, total, returned, query));
|
||||
};
|
||||
}
|
||||
if (length > search.getCount()) {
|
||||
// avoid condition 13 "Present request out-of-range"
|
||||
length = search.getCount();
|
||||
if (search.getCount() > 0) {
|
||||
logger.log(Level.INFO, "search returned " + 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() {
|
||||
|
@ -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,20 +459,16 @@ public class ZClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
public ZClient build() {
|
||||
try {
|
||||
return new ZClient(host, port, user, pass, timeout,
|
||||
preferredRecordSyntax,
|
||||
resultSetName,
|
||||
elementSetName,
|
||||
encoding,
|
||||
format,
|
||||
type,
|
||||
databases,
|
||||
preferredMessageSize,
|
||||
initListener);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
return new ZClient(host, port, user, pass, timeout,
|
||||
preferredRecordSyntax,
|
||||
resultSetName,
|
||||
elementSetName,
|
||||
encoding,
|
||||
format,
|
||||
type,
|
||||
databases,
|
||||
preferredMessageSize,
|
||||
initListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
69
z3950/src/main/java/org/xbib/io/iso23950/netty/Client.java
Normal file
69
z3950/src/main/java/org/xbib/io/iso23950/netty/Client.java
Normal 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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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")) {
|
||||
|
|
Loading…
Reference in a new issue