All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.spotify.helios.servicescommon.PersistentAtomicReference Maven / Gradle / Ivy

There is a newer version: 0.9.283
Show newest version
/*
 * Copyright (c) 2014 Spotify AB.
 *
 * 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 com.spotify.helios.servicescommon;

import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.spotify.helios.common.Json;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

import static com.google.common.base.Charsets.UTF_8;
import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

/**
 * A class that is similar to {@code AtomicReference} but is backed by a file, so can be
 * persisted across a server restart.  Assumes the underlying type can be serialized by Jackson.
 *
 * Strangely, this is not actually atomic in the {@code AtomicReference} way; i.e. not threadsafe,
 * nor does it do CAS.
 */
public class PersistentAtomicReference {

  private static final Logger log = LoggerFactory.getLogger(PersistentAtomicReference.class);

  private final Path filename;
  private final Path tempfilename;
  private final Object sync = new Object();

  private volatile T value;

  private PersistentAtomicReference(final Path filename,
                                    final JavaType javaType,
                                    final Supplier initialValue)
      throws IOException, InterruptedException {
    try {
      this.filename = filename.toAbsolutePath();
      this.tempfilename = filename.getFileSystem().getPath(this.filename.toString() + ".tmp");
      if (Files.exists(filename)) {
        final byte[] bytes = Files.readAllBytes(filename);
        if (bytes.length > 0) {
          value = Json.read(bytes, javaType);
        } else {
          value = initialValue.get();
        }
      } else {
        value = initialValue.get();
      }
    } catch (InterruptedIOException | ClosedByInterruptException e) {
      throw new InterruptedException(e.getMessage());
    }
  }

  /**
   * Set the reference to {@code newValue}.
   *
   * @param newValue The value to set.
   * @throws IOException If an error occurs working with the file on disk.
   * @throws InterruptedException If the thread is interrupted.
   */
  public void set(T newValue) throws IOException, InterruptedException {
    try {
      set0(newValue);
    } catch (InterruptedIOException | ClosedByInterruptException e) {
      throw new InterruptedException(e.getMessage());
    }
  }

  private void set0(final T newValue) throws IOException {
    log.debug("set: ({}) {}", filename, newValue);
    synchronized (sync) {
      final String json = Json.asPrettyStringUnchecked(newValue);
      log.debug("write: ({}) {}", tempfilename, json);
      Files.write(tempfilename, json.getBytes(UTF_8));
      log.debug("move: {} -> {}", tempfilename, filename);
      Files.move(tempfilename, filename, ATOMIC_MOVE, REPLACE_EXISTING);
      this.value = newValue;
    }
  }

  /**
   * Set the reference to {@code newValue}, and wraps {@link IOException}s in
   * {@link RuntimeException}s.
   *
   * @param newValue The value to set.
   * @throws InterruptedException If the thread is interrupted.
   */
  public void setUnchecked(T newValue) throws InterruptedException {
    try {
      set(newValue);
    } catch (IOException e) {
      throw Throwables.propagate(e);
    }
  }

  /**
   * Returns the value stored.
   *
   * @return The value.
   */
  public T get() {
    return value;
  }

  public static  PersistentAtomicReference create(final Path filename,
                                                        final TypeReference typeReference,
                                                        final Supplier initialValue)
      throws IOException, InterruptedException {

    return new PersistentAtomicReference<>(filename, Json.type(typeReference), initialValue);
  }


  public static  PersistentAtomicReference create(final String filename,
                                                        final TypeReference typeReference,
                                                        final Supplier initialValue)
      throws IOException, InterruptedException {
    return create(FileSystems.getDefault().getPath(filename), typeReference, initialValue);
  }

  public static  PersistentAtomicReference create(final Path filename,
                                                        final JavaType javaType,
                                                        final Supplier initialValue)
      throws IOException, InterruptedException {
    return new PersistentAtomicReference<>(filename, javaType, initialValue);
  }


  public static  PersistentAtomicReference create(final String filename,
                                                        final JavaType javaType,
                                                        final Supplier initialValue)
      throws IOException, InterruptedException {
    return create(FileSystems.getDefault().getPath(filename), javaType, initialValue);
  }

  @Override
  public String toString() {
    return Objects.toStringHelper(this)
        .add("filename", filename)
        .toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy