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
name = z3950
version = 5.0.4
version = 5.0.5
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 final String host;
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 Builder builder;
private final Lock lock;
@ -75,34 +49,8 @@ public class JDKZClient implements Client, Closeable {
private OutputStreamBERWriter berWriter;
private JDKZClient(String host,
int port,
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;
private JDKZClient(Builder builder) {
this.builder = builder;
this.lock = new ReentrantLock();
}
@ -117,7 +65,8 @@ public class JDKZClient implements Client, Closeable {
ensureConnected();
try {
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);
if (!success) {
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) {
PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax);
builder.resultSetName, builder.elementSetName, builder.preferredRecordSyntax);
if (offset < 1) {
// Z39.50 present bails out when offset = 0
offset = 1;
@ -164,7 +113,8 @@ public class JDKZClient implements Client, Closeable {
ensureConnected();
try {
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);
if (!search.isSuccess()) {
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) {
logger.log(Level.FINE, "search returned " + search.getCount());
PresentOperation present = new PresentOperation(berReader, berWriter,
resultSetName, elementSetName, preferredRecordSyntax);
builder.resultSetName, builder.elementSetName, builder.preferredRecordSyntax);
if (offset < 1) {
// Z39.50 bails out when offset = 0
offset = 1;
@ -211,7 +161,7 @@ public class JDKZClient implements Client, Closeable {
ensureConnected();
try {
lock.lock();
ScanOperation scanOperation = new ScanOperation(berReader, berWriter, databases);
ScanOperation scanOperation = new ScanOperation(berReader, berWriter, builder.databases);
scanOperation.executePQF(nTerms, step, position, query, scanListener);
} catch (SocketTimeoutException e) {
if (timeoutListener != null) {
@ -224,77 +174,78 @@ public class JDKZClient implements Client, Closeable {
@Override
public String getHost() {
return host;
return builder.host;
}
@Override
public int getPort() {
return port;
return builder.port;
}
@Override
public String getUser() {
return user;
return builder.user;
}
@Override
public String getPass() {
return pass;
return builder.pass;
}
@Override
public long getTimeout() {
return timeout;
return builder.timeout;
}
@Override
public String getPreferredRecordSyntax() {
return preferredRecordSyntax;
return builder.preferredRecordSyntax;
}
@Override
public String getResultSetName() {
return resultSetName;
return builder.resultSetName;
}
@Override
public String getElementSetName() {
return elementSetName;
return builder.elementSetName;
}
@Override
public String getEncoding() {
return encoding;
return builder.encoding;
}
@Override
public String getFormat() {
return format;
return builder.format;
}
@Override
public String getType() {
return type;
return builder.type;
}
@Override
public List<String> getDatabases() {
return databases;
return builder.databases;
}
public void connect() throws IOException {
try {
lock.lock();
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), (int) timeout); // in milliseconds
socket.setSoTimeout((int) timeout); // timeout in milliseconds
socket.connect(new InetSocketAddress(builder.host, builder.port), (int) builder.timeout); // in milliseconds
socket.setSoTimeout((int) builder.timeout); // timeout in milliseconds
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)) {
InitOperation initOperation = new InitOperation(berReader, berWriter, builder.user, builder.pass);
if (initOperation.execute(builder.preferredMessageSize,
builder.implementationName, builder.implementationVersion, builder.initListener)) {
throw new IOException("could not initiate connection");
}
logger.log(Level.INFO, initOperation.getTargetInfo());
@ -410,6 +361,10 @@ public class JDKZClient implements Client, Closeable {
private Integer preferredMessageSize;
private String implementationName;
private String implementationVersion;
private InitListener initListener;
private Builder() {
@ -421,6 +376,8 @@ public class JDKZClient implements Client, Closeable {
this.type = "Bibliographic";
this.databases = Collections.singletonList("");
this.preferredMessageSize = 10 * 1024 * 1024;
this.implementationName = "Java Z Client";
this.implementationVersion = "1.00";
}
public Builder setHost(String host) {
@ -491,22 +448,23 @@ public class JDKZClient implements Client, Closeable {
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) {
this.initListener = initListener;
return this;
}
public JDKZClient build() {
return new JDKZClient(host, port, user, pass, timeout,
preferredRecordSyntax,
resultSetName,
elementSetName,
encoding,
format,
type,
databases,
preferredMessageSize,
initListener);
return new JDKZClient(this);
}
}
}

View file

@ -49,9 +49,8 @@ public final class CQLRPNGenerator implements Visitor {
/**
* Context map.
*/
@SuppressWarnings("serial")
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("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();
BooleanOperator op = node.getBooleanGroup().getOperator();
switch (op) {
case AND:
rpn.c_rpnRpnOp.s_op.andOp = new ASN1Null();
break;
case OR:
rpn.c_rpnRpnOp.s_op.orOp = new ASN1Null();
break;
case NOT:
rpn.c_rpnRpnOp.s_op.andNotOp = new ASN1Null();
break;
default:
break;
case AND -> rpn.c_rpnRpnOp.s_op.andOp = new ASN1Null();
case OR -> rpn.c_rpnRpnOp.s_op.orOp = new ASN1Null();
case NOT -> rpn.c_rpnRpnOp.s_op.andNotOp = new ASN1Null();
default -> {
throw new UnsupportedOperationException("op: " + op);
}
}
rpn.c_rpnRpnOp.s_rpn1 = (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);
}
int attributeType = 2;
int attributeValue = 3;
int attributeValue = 3; // EQUALS, 2=3
switch (node.getComparitor()) {
case LESS: // 2=1
case LESS -> // 2=1
attributeValue = 1;
break;
case LESS_EQUALS: // 2=2
case LESS_EQUALS -> // 2=2
attributeValue = 2;
break;
case EQUALS: // 2=3
attributeValue = 3;
break;
case GREATER_EQUALS: // 2=4
case GREATER_EQUALS -> // 2=4
attributeValue = 4;
break;
case GREATER: // 2=5
case GREATER -> // 2=5
attributeValue = 5;
break;
case NOT_EQUALS: // 2=6
case NOT_EQUALS -> // 2=6
attributeValue = 6;
break;
case ALL: // 4=6
case ALL -> { // 4=6
attributeType = 4;
attributeValue = 6;
break;
case ANY: // 4=105
}
case ANY -> { // 4=104
attributeType = 4;
attributeValue = 104;
break;
default:
break;
}
default -> {
}
}
if (attributeValue != 3) {
AttributeElement ae = new AttributeElement();
@ -298,13 +284,11 @@ public final class CQLRPNGenerator implements Visitor {
if (context == null) {
context = "default"; // default context
}
int attributeType = 1; // use attribute set: bib-1 = 1
int attributeValue = getUseAttr(context, index.getName());
AttributeElement ae = new AttributeElement();
ae.attributeType = new ASN1Integer(attributeType);
ae.attributeValue = new AttributeElementAttributeValue();
ae.attributeValue.numeric = new ASN1Integer(attributeValue);
attributeElements.push(ae);
push(attributeElements, createAttributeElement(attributeType, attributeValue));
}
private int getUseAttr(String context, String attrName) {
@ -316,18 +300,31 @@ public final class CQLRPNGenerator implements Visitor {
}
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();
// let's derive attributes from the search term syntax
// relation attribute = 2
int attributeType = 2;
int attributeValue = 3; // equal = 3
int attributeValue = 3; // equal 2=3
push(attributeElements, createAttributeElement(attributeType, attributeValue));
// position attribute = 3
//attributeType = 3;
// attributeValue = 3; // any position = 3
//push(attributeElements, createAttributeElement(attributeType, attributeValue));
attributeType = 3;
attributeValue = 3; // any position = 3
if (v.startsWith("^")) {
attributeValue = 1; // first posiiton
v = v.substring(1);
}
push(attributeElements, createAttributeElement(attributeType, attributeValue));
// structure attribute = 4
attributeType = 4;
@ -336,6 +333,12 @@ public final class CQLRPNGenerator implements Visitor {
attributeValue = 1; // phrase
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));
// truncation attribute = 5
@ -354,6 +357,16 @@ public final class CQLRPNGenerator implements Visitor {
v = v.substring(1);
}
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);
}

View file

@ -37,7 +37,10 @@ public class InitOperation extends AbstractOperation<InitializeResponse, Initial
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();
boolean[] version = new boolean[3];
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.value = new ASN1GeneralString("1");
init.implementationName = new InternationalString();
init.implementationName.value = new ASN1GeneralString("Java ZClient");
init.implementationName.value = new ASN1GeneralString(implementationName);
init.implementationVersion = new InternationalString();
init.implementationVersion.value = new ASN1GeneralString("1.00");
init.implementationVersion.value = new ASN1GeneralString(implementationVersion);
if (user != null) {
init.idAuthentication = new IdAuthentication();
init.idAuthentication.idPass = new IdAuthenticationIdPass();