// Copyright 2012 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.enterprise.connector.db;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import com.google.common.io.FileBackedOutputStream;
import com.google.common.io.InputSupplier;
import com.google.enterprise.connector.spi.RepositoryDocumentException;
import com.google.enterprise.connector.spi.Value;
import com.google.enterprise.connector.spiimpl.BinaryValue;
import com.google.enterprise.connector.util.Base64;
import com.google.enterprise.connector.util.Base64DecoderException;
import com.google.enterprise.connector.util.Base64FilterInputStream;
import com.google.enterprise.connector.util.InputStreamFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
/** A factory and utility class for {@code InputStreamFactory}. */
/* TODO(jlacey): Move this to CM util package if another connector wants it. */
public class InputStreamFactories {
private static final Logger LOG =
Logger.getLogger(InputStreamFactories.class.getName());
/**
* Gets an {@code InputStreamFactory} that tries not to consume large
* amounts of memory, no matter how large the given byte array is.
*
* @param data a byte array
*/
public static final InputStreamFactory newInstance(byte[] data) {
if (data == null) {
return null;
}
try {
return new FileBackedInputStreamFactory(data);
} catch (IOException e) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.WARNING, "Failed to cache document content.", e);
} else {
LOG.warning("Failed to cache document content:\n" + e.toString());
}
// Resort to holding the binary data in memory, rather than on disk.
return new ByteArrayInputStreamFactory(data);
}
}
/**
* Gets an {@code InputStreamFactory} for a Base64-encoded string.
* If the input is not Base64-encoded, it is converted to bytes
* using UTF-8.
*/
public static final InputStreamFactory fromBase64String(String content) {
byte[] bytes = content.getBytes(Charsets.UTF_8);
byte[] decodedBytes;
try {
decodedBytes = Base64.decode(bytes);
} catch (Base64DecoderException e) {
// Just leave the data as-is.
decodedBytes = bytes;
}
return newInstance(decodedBytes);
}
/** Fully reads an input stream from the factory and Base64 encodes it. */
public static final String toBase64String(InputStreamFactory factory)
throws IOException {
return CharStreams.toString(new InputStreamReader(
new Base64FilterInputStream(factory.getInputStream()),
Charsets.UTF_8));
}
/** Fully reads an input stream from the value and Base64 encodes it. */
public static final String toString(Value binaryValue)
throws IOException, RepositoryDocumentException {
return CharStreams.toString(new InputStreamReader(
((BinaryValue) binaryValue).getInputStream(),
Charsets.UTF_8));
}
public static interface ContentLengthInputStreamFactory
extends InputStreamFactory {
/**
* Returns the number of bytes of content that will be returned by the
* InputStream, or -1 if the length is not known.
*/
public long length();
}
/** An InputStreamFactory backed by a FileBackedOutputStream. */
private static class FileBackedInputStreamFactory
implements ContentLengthInputStreamFactory {
private static final int IN_MEMORY_THRESHOLD = 32 * 1024;
/**
* We hold onto a single supplier, because when that gets finalized,
* the backing file will get deleted.
*/
private final InputSupplier<InputStream> supplier;
private final long length;
FileBackedInputStreamFactory(byte[] data) throws IOException {
FileBackedOutputStream out =
new FileBackedOutputStream(IN_MEMORY_THRESHOLD, true);
length = data.length;
out.write(data);
out.close();
supplier = out.getSupplier();
}
@Override
public InputStream getInputStream() throws IOException {
return supplier.getInput();
}
@Override
public long length() {
return length;
}
}
/** An InputStreamFactory backed by a byte array. */
private static class ByteArrayInputStreamFactory
implements ContentLengthInputStreamFactory {
private final byte[] data;
ByteArrayInputStreamFactory(byte[] data) {
// TODO(jlacey): Make a defensive copy of the array?
this.data = data;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(data);
}
@Override
public long length() {
return data.length;
}
}
/** This class should not be instantiated. */
private InputStreamFactories() {
throw new AssertionError();
}
}