fix CQL -> RPN generator, make implementation name configurable

This commit is contained in:
Jörg Prante 2023-03-27 17:11:06 +02:00
parent b17c13def4
commit f27a11af50
4 changed files with 111 additions and 137 deletions

View file

@ -1,5 +1,5 @@
group = org.xbib group = org.xbib
name = z3950 name = z3950
version = 5.0.4 version = 5.0.5
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL

View file

@ -39,33 +39,7 @@ public class JDKZClient implements Client, Closeable {
private static final Logger logger = Logger.getLogger(JDKZClient.class.getName()); private static final Logger logger = Logger.getLogger(JDKZClient.class.getName());
private final String host; private final Builder builder;
private final int port;
private final String user;
private final String pass;
private final long timeout;
private final String preferredRecordSyntax;
private final String resultSetName;
private final String elementSetName;
private final String encoding;
private final String format;
private final String type;
private final List<String> databases;
private final Integer preferredMessageSize;
private final InitListener initListener;
private final Lock lock; private final Lock lock;
@ -75,34 +49,8 @@ public class JDKZClient implements Client, Closeable {
private OutputStreamBERWriter berWriter; private OutputStreamBERWriter berWriter;
private JDKZClient(String host, private JDKZClient(Builder builder) {
int port, this.builder = builder;
String user,
String pass,
long timeout,
String preferredRecordSyntax,
String resultSetName,
String elementSetName,
String encoding,
String format,
String type,
List<String> databases,
Integer preferredMessageSize,
InitListener initListener) {
this.host = host;
this.port = port;
this.user = user;
this.pass = pass;
this.timeout = timeout;
this.preferredRecordSyntax = preferredRecordSyntax;
this.resultSetName = resultSetName;
this.elementSetName = elementSetName;
this.encoding = encoding;
this.format = format;
this.type = type;
this.databases = databases;
this.preferredMessageSize = preferredMessageSize;
this.initListener = initListener;
this.lock = new ReentrantLock(); this.lock = new ReentrantLock();
} }
@ -117,7 +65,8 @@ public class JDKZClient implements Client, Closeable {
ensureConnected(); ensureConnected();
try { try {
lock.lock(); lock.lock();
SearchOperation searchOperation = new SearchOperation(berReader, berWriter, resultSetName, databases, host); SearchOperation searchOperation = new SearchOperation(berReader, berWriter,
builder.resultSetName, builder.databases, builder.host);
boolean success = searchOperation.executeCQL(query); boolean success = searchOperation.executeCQL(query);
if (!success) { if (!success) {
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query)); logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
@ -130,7 +79,7 @@ public class JDKZClient implements Client, Closeable {
} }
if (searchOperation.getCount() > 0) { if (searchOperation.getCount() > 0) {
PresentOperation present = new PresentOperation(berReader, berWriter, PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax); builder.resultSetName, builder.elementSetName, builder.preferredRecordSyntax);
if (offset < 1) { if (offset < 1) {
// Z39.50 present bails out when offset = 0 // Z39.50 present bails out when offset = 0
offset = 1; offset = 1;
@ -164,7 +113,8 @@ public class JDKZClient implements Client, Closeable {
ensureConnected(); ensureConnected();
try { try {
lock.lock(); lock.lock();
SearchOperation search = new SearchOperation(berReader, berWriter, resultSetName, databases, host); SearchOperation search = new SearchOperation(berReader, berWriter,
builder.resultSetName, builder.databases, builder.host);
search.executePQF(query, StandardCharsets.UTF_8); search.executePQF(query, StandardCharsets.UTF_8);
if (!search.isSuccess()) { if (!search.isSuccess()) {
logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query)); logger.log(Level.WARNING, MessageFormat.format("search was not a success [{0}]", query));
@ -178,7 +128,7 @@ public class JDKZClient implements Client, Closeable {
if (search.getCount() > 0) { if (search.getCount() > 0) {
logger.log(Level.FINE, "search returned " + search.getCount()); logger.log(Level.FINE, "search returned " + search.getCount());
PresentOperation present = new PresentOperation(berReader, berWriter, PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax); builder.resultSetName, builder.elementSetName, builder.preferredRecordSyntax);
if (offset < 1) { if (offset < 1) {
// Z39.50 bails out when offset = 0 // Z39.50 bails out when offset = 0
offset = 1; offset = 1;
@ -211,7 +161,7 @@ public class JDKZClient implements Client, Closeable {
ensureConnected(); ensureConnected();
try { try {
lock.lock(); lock.lock();
ScanOperation scanOperation = new ScanOperation(berReader, berWriter, databases); ScanOperation scanOperation = new ScanOperation(berReader, berWriter, builder.databases);
scanOperation.executePQF(nTerms, step, position, query, scanListener); scanOperation.executePQF(nTerms, step, position, query, scanListener);
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
if (timeoutListener != null) { if (timeoutListener != null) {
@ -224,77 +174,78 @@ public class JDKZClient implements Client, Closeable {
@Override @Override
public String getHost() { public String getHost() {
return host; return builder.host;
} }
@Override @Override
public int getPort() { public int getPort() {
return port; return builder.port;
} }
@Override @Override
public String getUser() { public String getUser() {
return user; return builder.user;
} }
@Override @Override
public String getPass() { public String getPass() {
return pass; return builder.pass;
} }
@Override @Override
public long getTimeout() { public long getTimeout() {
return timeout; return builder.timeout;
} }
@Override @Override
public String getPreferredRecordSyntax() { public String getPreferredRecordSyntax() {
return preferredRecordSyntax; return builder.preferredRecordSyntax;
} }
@Override @Override
public String getResultSetName() { public String getResultSetName() {
return resultSetName; return builder.resultSetName;
} }
@Override @Override
public String getElementSetName() { public String getElementSetName() {
return elementSetName; return builder.elementSetName;
} }
@Override @Override
public String getEncoding() { public String getEncoding() {
return encoding; return builder.encoding;
} }
@Override @Override
public String getFormat() { public String getFormat() {
return format; return builder.format;
} }
@Override @Override
public String getType() { public String getType() {
return type; return builder.type;
} }
@Override @Override
public List<String> getDatabases() { public List<String> getDatabases() {
return databases; return builder.databases;
} }
public void connect() throws IOException { public void connect() throws IOException {
try { try {
lock.lock(); lock.lock();
Socket socket = new Socket(); Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), (int) timeout); // in milliseconds socket.connect(new InetSocketAddress(builder.host, builder.port), (int) builder.timeout); // in milliseconds
socket.setSoTimeout((int) timeout); // timeout in milliseconds socket.setSoTimeout((int) builder.timeout); // timeout in milliseconds
this.socket = socket; this.socket = socket;
InputStream src = new BufferedInputStream(socket.getInputStream()); InputStream src = new BufferedInputStream(socket.getInputStream());
OutputStream dest = new BufferedOutputStream(socket.getOutputStream()); OutputStream dest = new BufferedOutputStream(socket.getOutputStream());
this.berReader = new InputStreamBERReader(src); this.berReader = new InputStreamBERReader(src);
this.berWriter = new OutputStreamBERWriter(dest); this.berWriter = new OutputStreamBERWriter(dest);
InitOperation initOperation = new InitOperation(berReader, berWriter, user, pass); InitOperation initOperation = new InitOperation(berReader, berWriter, builder.user, builder.pass);
if (initOperation.execute(preferredMessageSize, initListener)) { if (initOperation.execute(builder.preferredMessageSize,
builder.implementationName, builder.implementationVersion, builder.initListener)) {
throw new IOException("could not initiate connection"); throw new IOException("could not initiate connection");
} }
logger.log(Level.INFO, initOperation.getTargetInfo()); logger.log(Level.INFO, initOperation.getTargetInfo());
@ -410,6 +361,10 @@ public class JDKZClient implements Client, Closeable {
private Integer preferredMessageSize; private Integer preferredMessageSize;
private String implementationName;
private String implementationVersion;
private InitListener initListener; private InitListener initListener;
private Builder() { private Builder() {
@ -421,6 +376,8 @@ public class JDKZClient implements Client, Closeable {
this.type = "Bibliographic"; this.type = "Bibliographic";
this.databases = Collections.singletonList(""); this.databases = Collections.singletonList("");
this.preferredMessageSize = 10 * 1024 * 1024; this.preferredMessageSize = 10 * 1024 * 1024;
this.implementationName = "Java Z Client";
this.implementationVersion = "1.00";
} }
public Builder setHost(String host) { public Builder setHost(String host) {
@ -491,22 +448,23 @@ public class JDKZClient implements Client, Closeable {
return this; return this;
} }
public Builder setImplementationName(String implementationName) {
this.implementationName = implementationName;
return this;
}
public Builder setImplementationVersion(String implementationVersion) {
this.implementationVersion = implementationVersion;
return this;
}
public Builder setInitListener(InitListener initListener) { public Builder setInitListener(InitListener initListener) {
this.initListener = initListener; this.initListener = initListener;
return this; return this;
} }
public JDKZClient build() { public JDKZClient build() {
return new JDKZClient(host, port, user, pass, timeout, return new JDKZClient(this);
preferredRecordSyntax,
resultSetName,
elementSetName,
encoding,
format,
type,
databases,
preferredMessageSize,
initListener);
} }
} }
} }

View file

@ -49,9 +49,8 @@ public final class CQLRPNGenerator implements Visitor {
/** /**
* Context map. * Context map.
*/ */
@SuppressWarnings("serial")
private final Map<String, ResourceBundle> contexts = new HashMap<>() { private final Map<String, ResourceBundle> contexts = new HashMap<>() {
private static final long serialVersionUID = 8199395368653216950L;
{ {
put("default", ResourceBundle.getBundle("org.xbib.z3950.common.cql.default")); put("default", ResourceBundle.getBundle("org.xbib.z3950.common.cql.default"));
put("cql", ResourceBundle.getBundle("org.xbib.z3950.common.cql.cql")); put("cql", ResourceBundle.getBundle("org.xbib.z3950.common.cql.cql"));
@ -156,17 +155,12 @@ public final class CQLRPNGenerator implements Visitor {
rpn.c_rpnRpnOp.s_op = new Operator(); rpn.c_rpnRpnOp.s_op = new Operator();
BooleanOperator op = node.getBooleanGroup().getOperator(); BooleanOperator op = node.getBooleanGroup().getOperator();
switch (op) { switch (op) {
case AND: case AND -> rpn.c_rpnRpnOp.s_op.andOp = new ASN1Null();
rpn.c_rpnRpnOp.s_op.andOp = new ASN1Null(); case OR -> rpn.c_rpnRpnOp.s_op.orOp = new ASN1Null();
break; case NOT -> rpn.c_rpnRpnOp.s_op.andNotOp = new ASN1Null();
case OR: default -> {
rpn.c_rpnRpnOp.s_op.orOp = new ASN1Null(); throw new UnsupportedOperationException("op: " + op);
break; }
case NOT:
rpn.c_rpnRpnOp.s_op.andNotOp = new ASN1Null();
break;
default:
break;
} }
rpn.c_rpnRpnOp.s_rpn1 = (RPNStructure) result.pop(); rpn.c_rpnRpnOp.s_rpn1 = (RPNStructure) result.pop();
rpn.c_rpnRpnOp.s_rpn2 = (RPNStructure) result.pop(); rpn.c_rpnRpnOp.s_rpn2 = (RPNStructure) result.pop();
@ -218,36 +212,28 @@ public final class CQLRPNGenerator implements Visitor {
node.getModifierList().accept(this); node.getModifierList().accept(this);
} }
int attributeType = 2; int attributeType = 2;
int attributeValue = 3; int attributeValue = 3; // EQUALS, 2=3
switch (node.getComparitor()) { switch (node.getComparitor()) {
case LESS: // 2=1 case LESS -> // 2=1
attributeValue = 1; attributeValue = 1;
break; case LESS_EQUALS -> // 2=2
case LESS_EQUALS: // 2=2 attributeValue = 2;
attributeValue = 2; case GREATER_EQUALS -> // 2=4
break; attributeValue = 4;
case EQUALS: // 2=3 case GREATER -> // 2=5
attributeValue = 3; attributeValue = 5;
break; case NOT_EQUALS -> // 2=6
case GREATER_EQUALS: // 2=4 attributeValue = 6;
attributeValue = 4; case ALL -> { // 4=6
break;
case GREATER: // 2=5
attributeValue = 5;
break;
case NOT_EQUALS: // 2=6
attributeValue = 6;
break;
case ALL: // 4=6
attributeType = 4; attributeType = 4;
attributeValue = 6; attributeValue = 6;
break; }
case ANY: // 4=105 case ANY -> { // 4=104
attributeType = 4; attributeType = 4;
attributeValue = 104; attributeValue = 104;
break; }
default: default -> {
break; }
} }
if (attributeValue != 3) { if (attributeValue != 3) {
AttributeElement ae = new AttributeElement(); AttributeElement ae = new AttributeElement();
@ -298,13 +284,11 @@ public final class CQLRPNGenerator implements Visitor {
if (context == null) { if (context == null) {
context = "default"; // default context context = "default"; // default context
} }
int attributeType = 1; // use attribute set: bib-1 = 1 int attributeType = 1; // use attribute set: bib-1 = 1
int attributeValue = getUseAttr(context, index.getName()); int attributeValue = getUseAttr(context, index.getName());
AttributeElement ae = new AttributeElement();
ae.attributeType = new ASN1Integer(attributeType); push(attributeElements, createAttributeElement(attributeType, attributeValue));
ae.attributeValue = new AttributeElementAttributeValue();
ae.attributeValue.numeric = new ASN1Integer(attributeValue);
attributeElements.push(ae);
} }
private int getUseAttr(String context, String attrName) { private int getUseAttr(String context, String attrName) {
@ -316,18 +300,31 @@ public final class CQLRPNGenerator implements Visitor {
} }
private ASN1OctetString transformTerm(Term term) { private ASN1OctetString transformTerm(Term term) {
int firstAttributeValue = 0;
if (!attributeElements.isEmpty()) {
AttributeElement attributeElement = attributeElements.peek();
if (attributeElement.attributeType.get() == 1) {
firstAttributeValue = attributeElement.attributeValue.numeric.get();
}
}
String v = term.getValue(); String v = term.getValue();
// let's derive attributes from the search term syntax // let's derive attributes from the search term syntax
// relation attribute = 2 // relation attribute = 2
int attributeType = 2; int attributeType = 2;
int attributeValue = 3; // equal = 3 int attributeValue = 3; // equal 2=3
push(attributeElements, createAttributeElement(attributeType, attributeValue)); push(attributeElements, createAttributeElement(attributeType, attributeValue));
// position attribute = 3 // position attribute = 3
//attributeType = 3; attributeType = 3;
// attributeValue = 3; // any position = 3 attributeValue = 3; // any position = 3
//push(attributeElements, createAttributeElement(attributeType, attributeValue)); if (v.startsWith("^")) {
attributeValue = 1; // first posiiton
v = v.substring(1);
}
push(attributeElements, createAttributeElement(attributeType, attributeValue));
// structure attribute = 4 // structure attribute = 4
attributeType = 4; attributeType = 4;
@ -336,6 +333,12 @@ public final class CQLRPNGenerator implements Visitor {
attributeValue = 1; // phrase attributeValue = 1; // phrase
v = v.substring(1, v.length()-1); v = v.substring(1, v.length()-1);
} }
if (firstAttributeValue == 31) {
attributeValue = 5; // date (normalized) = 5
}
if (firstAttributeValue == 1016) {
attributeValue = 6; // word list
}
push(attributeElements, createAttributeElement(attributeType, attributeValue)); push(attributeElements, createAttributeElement(attributeType, attributeValue));
// truncation attribute = 5 // truncation attribute = 5
@ -354,6 +357,16 @@ public final class CQLRPNGenerator implements Visitor {
v = v.substring(1); v = v.substring(1);
} }
push(attributeElements, createAttributeElement(attributeType, attributeValue)); push(attributeElements, createAttributeElement(attributeType, attributeValue));
// completeness attribute = 6
attributeType = 6;
attributeValue = 1; // not complete = 1
if (v.startsWith("^") && v.endsWith("$")) {
attributeValue = 3; // complete = 3
v = v.substring(1, v.length() - 1);
}
push(attributeElements, createAttributeElement(attributeType, attributeValue));
return new ASN1OctetString(v); return new ASN1OctetString(v);
} }

View file

@ -37,7 +37,10 @@ public class InitOperation extends AbstractOperation<InitializeResponse, Initial
this.pass = pass; this.pass = pass;
} }
public boolean execute(Integer preferredMessageSize, InitListener initListener) throws IOException { public boolean execute(Integer preferredMessageSize,
String implementationName,
String implementationVersion,
InitListener initListener) throws IOException {
InitializeRequest init = new InitializeRequest(); InitializeRequest init = new InitializeRequest();
boolean[] version = new boolean[3]; boolean[] version = new boolean[3];
version[0] = true; // any version, should alwasy be true version[0] = true; // any version, should alwasy be true
@ -68,9 +71,9 @@ public class InitOperation extends AbstractOperation<InitializeResponse, Initial
init.implementationId = new InternationalString(); init.implementationId = new InternationalString();
init.implementationId.value = new ASN1GeneralString("1"); init.implementationId.value = new ASN1GeneralString("1");
init.implementationName = new InternationalString(); init.implementationName = new InternationalString();
init.implementationName.value = new ASN1GeneralString("Java ZClient"); init.implementationName.value = new ASN1GeneralString(implementationName);
init.implementationVersion = new InternationalString(); init.implementationVersion = new InternationalString();
init.implementationVersion.value = new ASN1GeneralString("1.00"); init.implementationVersion.value = new ASN1GeneralString(implementationVersion);
if (user != null) { if (user != null) {
init.idAuthentication = new IdAuthentication(); init.idAuthentication = new IdAuthentication();
init.idAuthentication.idPass = new IdAuthenticationIdPass(); init.idAuthentication.idPass = new IdAuthenticationIdPass();