diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1BitString.java b/asn1/src/main/java/org/xbib/asn1/ASN1BitString.java index 6dc1d72..8461a5a 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1BitString.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1BitString.java @@ -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); } diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1Boolean.java b/asn1/src/main/java/org/xbib/asn1/ASN1Boolean.java index aee3fe2..db68a4b 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1Boolean.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1Boolean.java @@ -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); } diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1Enumerated.java b/asn1/src/main/java/org/xbib/asn1/ASN1Enumerated.java index 1d58558..d214ea7 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1Enumerated.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1Enumerated.java @@ -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); } diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1Integer.java b/asn1/src/main/java/org/xbib/asn1/ASN1Integer.java index e54a0a2..3dbdb75 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1Integer.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1Integer.java @@ -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); } diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1ObjectIdentifier.java b/asn1/src/main/java/org/xbib/asn1/ASN1ObjectIdentifier.java index 81deae6..237fde8 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1ObjectIdentifier.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1ObjectIdentifier.java @@ -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); diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1OctetString.java b/asn1/src/main/java/org/xbib/asn1/ASN1OctetString.java index c3cfe6b..0a1aa22 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1OctetString.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1OctetString.java @@ -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)); diff --git a/asn1/src/main/java/org/xbib/asn1/ASN1VideotexString.java b/asn1/src/main/java/org/xbib/asn1/ASN1VideotexString.java index eea4112..6fdfa41 100644 --- a/asn1/src/main/java/org/xbib/asn1/ASN1VideotexString.java +++ b/asn1/src/main/java/org/xbib/asn1/ASN1VideotexString.java @@ -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() + diff --git a/asn1/src/main/java/org/xbib/asn1/BERConstructed.java b/asn1/src/main/java/org/xbib/asn1/BERConstructed.java index 14d8498..55850b3 100644 --- a/asn1/src/main/java/org/xbib/asn1/BERConstructed.java +++ b/asn1/src/main/java/org/xbib/asn1/BERConstructed.java @@ -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 must 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; } /** diff --git a/asn1/src/main/java/org/xbib/asn1/BEREncoding.java b/asn1/src/main/java/org/xbib/asn1/BEREncoding.java index 7a4d6b2..c879256 100644 --- a/asn1/src/main/java/org/xbib/asn1/BEREncoding.java +++ b/asn1/src/main/java/org/xbib/asn1/BEREncoding.java @@ -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 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 must 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. diff --git a/asn1/src/main/java/org/xbib/asn1/BERPrimitive.java b/asn1/src/main/java/org/xbib/asn1/BERPrimitive.java index c86b47b..5b59a47 100644 --- a/asn1/src/main/java/org/xbib/asn1/BERPrimitive.java +++ b/asn1/src/main/java/org/xbib/asn1/BERPrimitive.java @@ -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 must 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. diff --git a/asn1/src/main/java/org/xbib/asn1/io/BERReader.java b/asn1/src/main/java/org/xbib/asn1/io/BERReader.java new file mode 100644 index 0000000..8776e0e --- /dev/null +++ b/asn1/src/main/java/org/xbib/asn1/io/BERReader.java @@ -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; +} diff --git a/asn1/src/main/java/org/xbib/asn1/io/BERWriter.java b/asn1/src/main/java/org/xbib/asn1/io/BERWriter.java new file mode 100644 index 0000000..3d6f91c --- /dev/null +++ b/asn1/src/main/java/org/xbib/asn1/io/BERWriter.java @@ -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; +} diff --git a/asn1/src/main/java/org/xbib/asn1/io/InputStreamBERReader.java b/asn1/src/main/java/org/xbib/asn1/io/InputStreamBERReader.java new file mode 100644 index 0000000..926acb1 --- /dev/null +++ b/asn1/src/main/java/org/xbib/asn1/io/InputStreamBERReader.java @@ -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 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); + } + } +} diff --git a/asn1/src/main/java/org/xbib/asn1/io/OutputStreamBERWriter.java b/asn1/src/main/java/org/xbib/asn1/io/OutputStreamBERWriter.java new file mode 100644 index 0000000..8621a22 --- /dev/null +++ b/asn1/src/main/java/org/xbib/asn1/io/OutputStreamBERWriter.java @@ -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 must 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 must 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 must 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); + } + } +} diff --git a/build.gradle b/build.gradle index 8ddbf05..58dec43 100644 --- a/build.gradle +++ b/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 { diff --git a/gradle.properties b/gradle.properties index bf3b47f..ed6e0d5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c44b679..0d4a951 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a92b9c8..531c640 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/z3950/build.gradle b/z3950/build.gradle index d7620a0..90c80cf 100644 --- a/z3950/build.gradle +++ b/z3950/build.gradle @@ -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')}" } diff --git a/z3950/src/main/java/org/xbib/io/iso23950/ZClient.java b/z3950/src/main/java/org/xbib/io/iso23950/ZClient.java index 4f7302e..0479b5c 100644 --- a/z3950/src/main/java/org/xbib/io/iso23950/ZClient.java +++ b/z3950/src/main/java/org/xbib/io/iso23950/ZClient.java @@ -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 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 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); } } } diff --git a/z3950/src/main/java/org/xbib/io/iso23950/netty/Client.java b/z3950/src/main/java/org/xbib/io/iso23950/netty/Client.java new file mode 100644 index 0000000..d99010b --- /dev/null +++ b/z3950/src/main/java/org/xbib/io/iso23950/netty/Client.java @@ -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() { + 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 { + + } + } +} diff --git a/z3950/src/main/java/org/xbib/io/iso23950/operations/AbstractOperation.java b/z3950/src/main/java/org/xbib/io/iso23950/operations/AbstractOperation.java new file mode 100644 index 0000000..4e68918 --- /dev/null +++ b/z3950/src/main/java/org/xbib/io/iso23950/operations/AbstractOperation.java @@ -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); + } + } + +} diff --git a/z3950/src/main/java/org/xbib/io/iso23950/operations/InitOperation.java b/z3950/src/main/java/org/xbib/io/iso23950/operations/InitOperation.java index 2ac2b1b..e565863 100644 --- a/z3950/src/main/java/org/xbib/io/iso23950/operations/InitOperation.java +++ b/z3950/src/main/java/org/xbib/io/iso23950/operations/InitOperation.java @@ -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) { diff --git a/z3950/src/main/java/org/xbib/io/iso23950/operations/PresentOperation.java b/z3950/src/main/java/org/xbib/io/iso23950/operations/PresentOperation.java index 4556aa9..f469191 100644 --- a/z3950/src/main/java/org/xbib/io/iso23950/operations/PresentOperation.java +++ b/z3950/src/main/java/org/xbib/io/iso23950/operations/PresentOperation.java @@ -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; diff --git a/z3950/src/main/java/org/xbib/io/iso23950/operations/SearchOperation.java b/z3950/src/main/java/org/xbib/io/iso23950/operations/SearchOperation.java index bb89d6b..28fe693 100644 --- a/z3950/src/main/java/org/xbib/io/iso23950/operations/SearchOperation.java +++ b/z3950/src/main/java/org/xbib/io/iso23950/operations/SearchOperation.java @@ -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 results = new HashMap<>(); + private final Map results; - public boolean execute(ZClient client, RPNQuery rpn) throws IOException { + private final String resultSetName; + + private final List databases; + + private final String host; + + public SearchOperation(BERReader reader, BERWriter writer, + String resultSetName, + List 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 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; } diff --git a/z3950/src/test/java/org/xbib/io/iso23950/ZClientTest.java b/z3950/src/test/java/org/xbib/io/iso23950/ZClientTest.java index df55f7a..15f8e7c 100644 --- a/z3950/src/test/java/org/xbib/io/iso23950/ZClientTest.java +++ b/z3950/src/test/java/org/xbib/io/iso23950/ZClientTest.java @@ -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")) {