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();
}
}
}