/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 org.wildfly.security.util; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.wildfly.security._private.ElytronMessages; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ public final class AtomicFileOutputStream extends OutputStream { private volatile State current; private static final AtomicReferenceFieldUpdater<AtomicFileOutputStream, State> currentUpdater = AtomicReferenceFieldUpdater.newUpdater(AtomicFileOutputStream.class, State.class, "current"); public AtomicFileOutputStream(final String name) throws IOException { this(Paths.get(name)); } public AtomicFileOutputStream(final File file) throws IOException { this(file.toPath()); } public AtomicFileOutputStream(final Path path) throws IOException { final Path parent = path.getParent(); if (parent != null && parent.getNameCount() != 0 && ! Files.exists(parent)) { Files.createDirectories(parent); } current = new OpenState(Files.newOutputStream(path.resolveSibling(path.getFileName() + ".new"), CREATE, TRUNCATE_EXISTING), path); } public void flush() throws IOException { current.flush(); } public void close() throws IOException { current.close(); } public void write(final int b) throws IOException { current.write(b); } public void write(final byte[] bytes, final int off, final int len) throws IOException { current.write(bytes, off, len); } boolean casCurrent(State expect, State update) { return currentUpdater.compareAndSet(this, expect, update); } public void cancel() throws IOException { current.cancel(); } abstract static class State { abstract void write(int b) throws IOException; abstract void write(byte[] b, int off, int len) throws IOException; abstract void flush() throws IOException; abstract void close() throws IOException; abstract void cancel() throws IOException; } final class OpenState extends State { private final OutputStream delegate; private final Path path; OpenState(final OutputStream delegate, final Path path) { this.delegate = delegate; this.path = path; } void write(final int b) throws IOException { delegate.write(b); } void write(final byte[] b, final int off, final int len) throws IOException { delegate.write(b, off, len); } void flush() throws IOException { delegate.flush(); } void close() throws IOException { if (casCurrent(this, CLOSED)) { // atomic cleanup operation: close out our stream delegate.close(); final Path path = this.path; final Path newPath = path.resolveSibling(path.getFileName() + ".new"); try { // move new file in Files.move(newPath, path, REPLACE_EXISTING, ATOMIC_MOVE); } catch (Throwable t) { try { // didn't work, gotta delete our temp copy Files.deleteIfExists(newPath); } catch (Throwable problem) { problem.addSuppressed(t); throw problem; } throw t; } } } void cancel() throws IOException { if (casCurrent(this, CLOSED)) { delegate.close(); final Path newPath = path.resolveSibling(path.getFileName() + ".new"); Files.deleteIfExists(newPath); } } } private static final State CLOSED = new State() { void write(final int b) throws IOException { throw ElytronMessages.log.closed(); } void write(final byte[] b, final int off, final int len) throws IOException { throw ElytronMessages.log.closed(); } void flush() throws IOException { throw ElytronMessages.log.closed(); } void close() throws IOException { // no op } void cancel() throws IOException { // no op } }; }