Skip to content

Commit

Permalink
[bugfix] attempt to make sure streams are closed
Browse files Browse the repository at this point in the history
  • Loading branch information
line-o committed Nov 14, 2024
1 parent f2981e1 commit 3f8190c
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 97 deletions.
40 changes: 21 additions & 19 deletions exist-core/src/main/java/org/exist/xquery/ArrowOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,7 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP
}
contextSequence = leftExpr.eval(contextSequence, null);

final FunctionReference fref;
if (fcall != null) {
fref = new FunctionReference(this, fcall);
} else {
final Sequence funcSeq = funcSpec.eval(contextSequence, contextItem);
if (funcSeq.getCardinality() != Cardinality.EXACTLY_ONE)
{throw new XPathException(this, ErrorCodes.XPTY0004,
"Expected exactly one item for the function to be called, got " + funcSeq.getItemCount() +
". Expression: " + ExpressionDumper.dump(funcSpec));}
final Item item0 = funcSeq.itemAt(0);
if (!Type.subTypeOf(item0.getType(), Type.FUNCTION_REFERENCE)) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"Type error: expected function, got " + Type.getTypeName(item0.getType()));
}
fref = (FunctionReference)item0;
}
try {
try (final FunctionReference fref = getFunctionReference(contextSequence, contextItem)) {
final List<Expression> fparams = new ArrayList<>(parameters.size() + 1);
fparams.add(new ContextParam(context, contextSequence));
fparams.addAll(parameters);
Expand All @@ -122,11 +106,29 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP
fref.analyze(new AnalyzeContextInfo(cachedContextInfo));
// Evaluate the function
return fref.eval(null);
} finally {
fref.close();
}
}

private FunctionReference getFunctionReference(final Sequence contextSequence, final Item contextItem) throws XPathException {
if (fcall != null) {
return new FunctionReference(this, fcall);
}

final Sequence funcSeq = funcSpec.eval(contextSequence, contextItem);
if (funcSeq.getCardinality() != Cardinality.EXACTLY_ONE) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"Expected exactly one item for the function to be called, got " + funcSeq.getItemCount() +
". Expression: " + ExpressionDumper.dump(funcSpec));
}

final Item item = funcSeq.itemAt(0);
if (!Type.subTypeOf(item.getType(), Type.FUNCTION_REFERENCE)) {
throw new XPathException(this, ErrorCodes.XPTY0004,
"Type error: expected function, got " + Type.getTypeName(item.getType()));
}
return (FunctionReference) item;
}

@Override
public int returnsType() {
return fcall == null ? Type.ITEM : fcall.returnsType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,18 @@
package org.exist.xquery.functions.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Base64;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.dom.QName;
import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Base64BinaryValueType;
import org.exist.xquery.value.BinaryValue;
import org.exist.xquery.value.BinaryValueFromInputStream;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;

import static java.nio.charset.StandardCharsets.UTF_8;
import org.exist.xquery.*;
import org.exist.xquery.value.*;

public class BinaryToString extends BasicFunction {

Expand Down Expand Up @@ -95,37 +84,47 @@ public BinaryToString(XQueryContext context, FunctionSignature signature) {
}

@Override
public Sequence eval(Sequence[] args, Sequence contextSequence)
throws XPathException {
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {

if(args[0].isEmpty()) {
if (args[0].isEmpty()) {
return Sequence.EMPTY_SEQUENCE;
}
String encoding = UTF_8.name();
if(args.length == 2) {
encoding = args[1].getStringValue();
}
if(isCalledAs("binary-to-string")) {

final Charset encoding = getCharset(args);

if (isCalledAs("binary-to-string")) {
return binaryToString((BinaryValue) args[0].itemAt(0), encoding);
}

return stringToBinary(args[0].getStringValue(), encoding);
}

private Charset getCharset(Sequence[] args) throws XPathException {
final Charset encoding;
if (args.length == 2) {
final String stringValue = args[1].getStringValue();
try {
encoding = stringValue.isEmpty() ? StandardCharsets.UTF_8 : Charset.forName(stringValue);
} catch(final UnsupportedCharsetException e) {
throw new XPathException(this, UtilErrorCodes.UNRECOGNIZED_ENCODING, "Unsupported encoding: " + stringValue);
}
} else {
return stringToBinary(args[0].getStringValue(), encoding);
encoding = StandardCharsets.UTF_8;
}
return encoding;
}

protected StringValue binaryToString(BinaryValue binary, String encoding) throws XPathException {
protected StringValue binaryToString(final BinaryValue binary, final Charset encoding) throws XPathException {
try (final UnsynchronizedByteArrayOutputStream os = new UnsynchronizedByteArrayOutputStream()) {
binary.streamBinaryTo(os);
return new StringValue(this, os.toString(encoding));
} catch(final IOException ioe) {
throw new XPathException(this, ioe);
throw new XPathException(this, UtilErrorCodes.IO_ERROR, ioe);
}
}

protected BinaryValue stringToBinary(String str, String encoding) throws XPathException {
try {
return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), new UnsynchronizedByteArrayInputStream(str.getBytes(encoding)), this);
} catch(final UnsupportedEncodingException e) {
throw new XPathException(this, "Unsupported encoding: " + encoding);
}
protected BinaryValue stringToBinary(final String str, final Charset encoding) throws XPathException {
return new BinaryValueFromBinaryString(new Base64BinaryValueType(),
Base64.getEncoder().encodeToString(str.getBytes(encoding)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery.functions.util;

import org.exist.dom.QName;
import org.exist.xquery.ErrorCodes;

class UtilErrorCodes extends ErrorCodes.ErrorCode {
public static final ErrorCodes.ErrorCode UNRECOGNIZED_ENCODING = new UtilErrorCodes("UNRECOGNIZED_ENCODING",
"The encoding is not recognized.");

public static final ErrorCodes.ErrorCode IO_ERROR = new UtilErrorCodes("IO_ERROR",
"There was an issue accessing system resources.");

UtilErrorCodes(final String code, final String description) {
super(new QName(code, UtilModule.NAMESPACE_URI, UtilModule.PREFIX), description);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.exist.xquery.XPathException;

import java.io.*;
import java.nio.charset.Charset;

import static java.nio.charset.StandardCharsets.UTF_8;

Expand All @@ -45,6 +46,7 @@ public class BinaryValueFromBinaryString extends BinaryValue {

private final static Logger LOG = LogManager.getLogger(BinaryValueFromBinaryString.class);

// private final Charset encoding;
private final String value;
private boolean closed = false;

Expand All @@ -55,40 +57,26 @@ public BinaryValueFromBinaryString(BinaryValueType binaryValueType, String value
public BinaryValueFromBinaryString(final Expression expression, BinaryValueType binaryValueType, String value) throws XPathException {
super(expression, null, binaryValueType);
this.value = binaryValueType.verifyAndFormatString(value);
//this.encoding = Charset.defaultCharset();
}

public BinaryValueFromBinaryString(BinaryValueType binaryValueType, String value, Charset encoding) throws XPathException {
super(null, null, binaryValueType);
this.value = binaryValueType.verifyAndFormatString(value);
// this.encoding = encoding;
}

@Override
public BinaryValue convertTo(BinaryValueType binaryValueType) throws XPathException {
public BinaryValue convertTo(final BinaryValueType binaryValueType) throws XPathException {
//TODO temporary approach, consider implementing a TranscodingBinaryValueFromBinaryString(BinaryValueFromBinaryString) class
//that only does the transncoding lazily

final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream();
FilterOutputStream fos = null;
try {

//that only does the transcoding lazily
try (final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(); final FilterOutputStream fos = binaryValueType.getEncoder(baos)) {
//transcode
fos = binaryValueType.getEncoder(baos);
streamBinaryTo(fos);

return new BinaryValueFromBinaryString(getExpression(), binaryValueType, baos.toString(UTF_8));
} catch (final IOException ioe) {
throw new XPathException(getExpression(), ioe);
} finally {
if (fos != null) {
try {
fos.close();
} catch (final IOException ioe) {
LOG.error("Unable to close stream: {}", ioe.getMessage(), ioe);
}
}

try {
baos.close();
} catch (final IOException ioe) {
LOG.error("Unable to close stream: {}", ioe.getMessage(), ioe);
}
}

return new BinaryValueFromBinaryString(getExpression(), binaryValueType, baos.toString(UTF_8));
}

@Override
Expand All @@ -101,7 +89,7 @@ public void streamBinaryTo(OutputStream os) throws IOException {
final FilterOutputStream fos = getBinaryValueType().getDecoder(safeOutputStream);

//write with the decoder
final byte data[] = value.getBytes();
final byte[] data = value.getBytes();
fos.write(data);

//we do have to close the decoders output stream though
Expand All @@ -117,13 +105,13 @@ public void streamBinaryTo(OutputStream os) throws IOException {
@Override
public void streamTo(OutputStream os) throws IOException {
//write
final byte data[] = value.getBytes(); //TODO consider a more efficient approach for writing large strings
final byte[] data = value.getBytes(); //TODO consider a more efficient approach for writing large strings
os.write(data);
}

@Override
public InputStream getInputStream() {
//TODO consider a more efficient approach for writting large strings
//TODO consider a more efficient approach for writing large strings
final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream();
try {
streamBinaryTo(baos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
import org.exist.xquery.value.BinaryValue;
import org.exist.xquery.value.StringValue;
import org.junit.Test;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import static org.junit.Assert.assertEquals;

/**
Expand All @@ -36,9 +40,9 @@
public class BinaryToStringTest {

@Test
public void roundtrip() throws XPathException {
public void roundTrip() throws XPathException {
final String value = "hello world";
final String encoding = "UTF-8";
final Charset encoding = StandardCharsets.UTF_8;

TestableBinaryToString testable = new TestableBinaryToString(new MockXQueryContext(), null);

Expand All @@ -54,12 +58,12 @@ public TestableBinaryToString(XQueryContext context, FunctionSignature signature
}

@Override
public StringValue binaryToString(BinaryValue binary, String encoding) throws XPathException {
public StringValue binaryToString(BinaryValue binary, Charset encoding) throws XPathException {
return super.binaryToString(binary, encoding);
}

@Override
public BinaryValue stringToBinary(String str, String encoding) throws XPathException {
public BinaryValue stringToBinary(String str, Charset encoding) throws XPathException {
return super.stringToBinary(str, encoding);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@
import org.exist.xquery.value.BinaryValue;
import org.exist.xquery.value.BinaryValueFromInputStream;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;

import static java.util.zip.Deflater.DEFAULT_COMPRESSION;


/**
* Deflate compression
Expand All @@ -52,7 +53,7 @@ public class DeflateFunction extends BasicFunction
{
private final static QName DEFLATE_FUNCTION_NAME = new QName("deflate", CompressionModule.NAMESPACE_URI, CompressionModule.PREFIX);

public final static FunctionSignature signatures[] = {
public final static FunctionSignature[] signatures = {
new FunctionSignature(
DEFLATE_FUNCTION_NAME,
"Deflate data (RFC 1950)",
Expand Down Expand Up @@ -84,25 +85,29 @@ public DeflateFunction(XQueryContext context, FunctionSignature signature)
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException
{
// is there some data to Deflate?
if(args[0].isEmpty())
if (args[0].isEmpty()) {
return Sequence.EMPTY_SEQUENCE;
}

BinaryValue bin = (BinaryValue) args[0].itemAt(0);
final BinaryValue bin = (BinaryValue) args[0].itemAt(0);

boolean rawflag = false;
if(args.length > 1 && !args[1].isEmpty())
rawflag = args[1].itemAt(0).convertTo(Type.BOOLEAN).effectiveBooleanValue();
boolean rawflag = false;
if (args.length > 1 && !args[1].isEmpty()) {
rawflag = args[1].itemAt(0).convertTo(Type.BOOLEAN).effectiveBooleanValue();
}

Deflater defl = new Deflater(java.util.zip.Deflater.DEFAULT_COMPRESSION, rawflag);
final Deflater deflater = new Deflater(DEFAULT_COMPRESSION, rawflag);

// deflate the data
try(final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(baos, defl)) {
try (
final UnsynchronizedByteArrayOutputStream stream = new UnsynchronizedByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(stream, deflater)
) {
bin.streamBinaryTo(dos);
dos.flush();
dos.finish();

return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), baos.toInputStream(), this);
return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), stream.toInputStream(), this);
} catch(IOException ioe) {
throw new XPathException(this, ioe.getMessage(), ioe);
}
Expand Down

0 comments on commit 3f8190c

Please sign in to comment.