DigestSink.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tika.digest;
import java.io.IOException;
import java.io.OutputStream;
/**
* A sink returned by {@link Digester#digestSink}: bytes written to it are digested, and the
* value(s) reach the metadata only if the producer calls {@link #commit()}.
* <p>
* Commit is explicit because the alternative -- publish on close unless something remembered
* to cancel -- makes every unanticipated exit (a checked exception, an {@link Error}, a
* producer that closes the sink itself) publish a digest of whatever bytes happened to arrive.
* A digest of a partial write is worse than no digest: it is wrong, and it is stably wrong,
* so every failure of the same shape produces the same plausible value.
* <p>
* Contract:
* <ul>
* <li>Write the content, then {@link #commit()}, then {@link #close()} -- close in a
* {@code finally} or via try-with-resources.</li>
* <li>{@link #close()} releases resources either way, and publishes only if
* {@code commit()} was called first. It is idempotent.</li>
* <li>{@link #commit()} after {@code close()} throws {@link IllegalStateException}: the
* chance to publish is gone, and failing loudly beats a silently missing digest.</li>
* <li>Writing after {@code close()} throws {@link IOException}.</li>
* <li>All methods must be called from the producing thread.</li>
* </ul>
*/
public abstract class DigestSink extends OutputStream {
private boolean committed;
private boolean closed;
/**
* Marks the content complete, so {@link #close()} publishes it. Idempotent.
*
* @throws IllegalStateException if this sink is already closed
*/
public final void commit() {
if (closed) {
throw new IllegalStateException("cannot commit a closed digest sink");
}
committed = true;
}
@Override
public final void close() throws IOException {
if (closed) {
return;
}
closed = true;
finish(committed);
}
/**
* Called once, from the first {@link #close()}. Release resources either way; set the
* metadata only when {@code publish} is true.
*/
protected abstract void finish(boolean publish) throws IOException;
/** For subclasses' write methods. */
protected final void ensureOpen() throws IOException {
if (closed) {
throw new IOException("digest sink is closed");
}
}
}