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

ratpack.reload.internal.ReloadableFileBackedFactory Maven / Gradle / Ivy

/*
 * Copyright 2013 the original author or authors.
 *
 * 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 ratpack.reload.internal;

import ratpack.func.Factory;
import ratpack.util.internal.Paths2;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static ratpack.util.Exceptions.uncheck;

public class ReloadableFileBackedFactory implements Factory {

  // Note: we are blocking for IO on the main thread here, but it only really impacts
  // reloadable mode so it's not worth the complication of jumping off the main thread

  private final Path file;
  private final boolean reloadable;
  private final Producer producer;
  private final Releaser releaser;

  private final AtomicReference lastModifiedHolder = new AtomicReference<>(null);
  private final AtomicReference contentHolder = new AtomicReference<>();
  private final AtomicReference delegateHolder = new AtomicReference<>(null);
  private final Lock lock = new ReentrantLock();

  public interface Producer {
    T produce(Path file, String content) throws Exception;
  }

  public interface Releaser {
    void release(T thing);
  }

  private static class NullReleaser implements Releaser {
    @Override
    public void release(T thing) {

    }
  }

  public ReloadableFileBackedFactory(Path file, boolean reloadable, Producer producer) {
    this(file, reloadable, producer, new NullReleaser<>());
  }

  public ReloadableFileBackedFactory(Path file, boolean reloadable, Producer producer, Releaser releaser) {
    this.file = file;
    this.reloadable = reloadable;
    this.producer = producer;
    this.releaser = releaser;

    if (!reloadable) {
      try {
        refresh();
      } catch (Exception e) {
        throw uncheck(e);
      }
    }
  }

  public T create() {
    if (!reloadable) {
      return delegateHolder.get();
    }

    // If the file disappeared, wait a little for it to appear
    int i = 10;
    while (!Files.exists(file) && --i > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw uncheck(e);
      }
    }

    if (!Files.exists(file)) {
      return null;
    }

    try {
      if (refreshNeeded()) {
        refresh();
      }
    } catch (Exception e) {
      throw uncheck(e);
    }

    return delegateHolder.get();
  }

  private boolean refreshNeeded() throws IOException {
    return !Files.getLastModifiedTime(file).equals(lastModifiedHolder.get());
  }

  private void refresh() throws Exception {
    lock.lock();
    try {
      FileTime lastModifiedTime = Files.getLastModifiedTime(file);
      String content = Paths2.readText(file, StandardCharsets.UTF_8);

      if (lastModifiedTime.equals(lastModifiedHolder.get()) && content.equals(contentHolder.get())) {
        return;
      }

      T previous = delegateHolder.getAndSet(null);
      if (previous != null) {
        releaser.release(previous);
      }
      delegateHolder.set(producer.produce(file, content));

      this.lastModifiedHolder.set(lastModifiedTime);
      this.contentHolder.set(content);
    } finally {
      lock.unlock();
    }
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy