RRD4J 3.6 (ticket #2716)

This commit is contained in:
zzz
2020-05-28 10:33:03 +00:00
parent 686fe88e61
commit 4fe9a27e2e
20 changed files with 644 additions and 416 deletions

View File

@ -264,7 +264,7 @@ Applications:
See licenses/LICENSE-Apache2.0.txt See licenses/LICENSE-Apache2.0.txt
See licenses/LICENSE-ECLIPSE-1.0.html See licenses/LICENSE-ECLIPSE-1.0.html
RRD4J 3.5 (jrobin.jar): RRD4J 3.6 (jrobin.jar):
Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor.
Copyright (c) 2011 The OpenNMS Group, Inc. Copyright (c) 2011 The OpenNMS Group, Inc.
Copyright 2011 The RRD4J Authors. Copyright 2011 The RRD4J Authors.

View File

@ -114,7 +114,8 @@ public abstract class ByteBufferBackend extends RrdBackend {
*/ */
protected synchronized void read(long offset, byte[] b) throws IOException { protected synchronized void read(long offset, byte[] b) throws IOException {
checkOffsetAndByteBuffer(offset); checkOffsetAndByteBuffer(offset);
byteBuffer.get(b, (int) offset, b.length); byteBuffer.position((int)offset);
byteBuffer.get(b);
} }
@Override @Override

View File

@ -8,6 +8,7 @@ import java.io.OutputStream;
import org.rrd4j.ConsolFun; import org.rrd4j.ConsolFun;
import org.rrd4j.data.Aggregates; import org.rrd4j.data.Aggregates;
import org.rrd4j.data.DataProcessor; import org.rrd4j.data.DataProcessor;
import org.rrd4j.data.Variable;
/** /**
* Class used to represent data fetched from the RRD. * Class used to represent data fetched from the RRD.
@ -405,39 +406,40 @@ public class FetchData {
*/ */
public void exportXml(OutputStream outputStream) throws IOException { public void exportXml(OutputStream outputStream) throws IOException {
//No auto flush for XmlWriter, it will be flushed once, when export is finished //No auto flush for XmlWriter, it will be flushed once, when export is finished
XmlWriter writer = new XmlWriter(outputStream, false); try (XmlWriter writer = new XmlWriter(outputStream, false)) {
writer.startTag("fetch_data"); writer.startTag("fetch_data");
writer.startTag("request"); writer.startTag("request");
writer.writeTag("file", request.getParentDb().getPath()); writer.writeTag("file", request.getParentDb().getPath());
writer.writeComment(Util.getDate(request.getFetchStart())); writer.writeComment(Util.getDate(request.getFetchStart()));
writer.writeTag("start", request.getFetchStart()); writer.writeTag("start", request.getFetchStart());
writer.writeComment(Util.getDate(request.getFetchEnd())); writer.writeComment(Util.getDate(request.getFetchEnd()));
writer.writeTag("end", request.getFetchEnd()); writer.writeTag("end", request.getFetchEnd());
writer.writeTag("resolution", request.getResolution()); writer.writeTag("resolution", request.getResolution());
writer.writeTag("cf", request.getConsolFun()); writer.writeTag("cf", request.getConsolFun());
writer.closeTag(); // request writer.closeTag(); // request
writer.startTag("datasources"); writer.startTag("datasources");
for (String dsName : dsNames) { for (String dsName : dsNames) {
writer.writeTag("name", dsName); writer.writeTag("name", dsName);
}
writer.closeTag(); // datasources
writer.startTag("data");
for (int i = 0; i < timestamps.length; i++) {
writer.startTag("row");
writer.writeComment(Util.getDate(timestamps[i]));
writer.writeTag("timestamp", timestamps[i]);
writer.startTag("values");
for (int j = 0; j < dsNames.length; j++) {
writer.writeTag("v", values[j][i]);
} }
writer.closeTag(); // values writer.closeTag(); // datasources
writer.closeTag(); // row writer.startTag("data");
for (int i = 0; i < timestamps.length; i++) {
writer.startTag("row");
writer.writeComment(Util.getDate(timestamps[i]));
writer.writeTag("timestamp", timestamps[i]);
writer.startTag("values");
for (int j = 0; j < dsNames.length; j++) {
writer.writeTag("v", values[j][i]);
}
writer.closeTag(); // values
writer.closeTag(); // row
}
writer.closeTag(); // data
writer.closeTag(); // fetch_data
writer.flush();
} }
writer.closeTag(); // data
writer.closeTag(); // fetch_data
writer.flush();
} }
/** /**
* Dumps fetch data to file in XML format. * Dumps fetch data to file in XML format.
* *

View File

@ -339,10 +339,10 @@ public abstract class RrdBackend {
/** /**
* Extract a CharBuffer from the backend, used by readString * Extract a CharBuffer from the backend, used by readString
* *
* @param offset * @param offset the offset in the rrd
* @param size * @param size the size of the buffer, in character
* @return * @return a new CharBuffer
* @throws IOException * @throws IOException if the read fails
*/ */
protected CharBuffer getCharBuffer(long offset, int size) throws IOException { protected CharBuffer getCharBuffer(long offset, int size) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(size * 2); ByteBuffer bbuf = ByteBuffer.allocate(size * 2);

View File

@ -256,10 +256,10 @@ public abstract class RrdBackendFactory implements Closeable {
private static final Pattern URIPATTERN = Pattern.compile("^(?:(?<scheme>[a-zA-Z][a-zA-Z0-9+-\\.]*):)?(?://(?<authority>[^/\\?#]*))?(?<path>[^\\?#]*)(?:\\?(?<query>[^#]*))?(?:#(?<fragment>.*))?$"); private static final Pattern URIPATTERN = Pattern.compile("^(?:(?<scheme>[a-zA-Z][a-zA-Z0-9+-\\.]*):)?(?://(?<authority>[^/\\?#]*))?(?<path>[^\\?#]*)(?:\\?(?<query>[^#]*))?(?:#(?<fragment>.*))?$");
/** /**
* Try to detect an URI from a path. It's needed because of windows path that look's like an URI * Try to detect an URI from a path. It's needed because of Microsoft Windows path that look's like an URI
* and to URL-encode the path. * and to URL-encode the path.
* *
* @param rrdpath * @param rrdpath a file URI that can be a Windows path
* @return an URI * @return an URI
*/ */
public static URI buildGenericUri(String rrdpath) { public static URI buildGenericUri(String rrdpath) {
@ -380,9 +380,9 @@ public abstract class RrdBackendFactory implements Closeable {
* <li>query and fragment is kept as is. * <li>query and fragment is kept as is.
* </ul> * </ul>
* *
* @param rootUri * @param rootUri the URI to match against
* @param uri * @param uri an URI that the current backend can handle.
* @param relative * @param relative if true, return an URI relative to the {@code rootUri}
* @return a calculate normalized absolute URI or null if the tried URL don't match against the root. * @return a calculate normalized absolute URI or null if the tried URL don't match against the root.
*/ */
protected URI resolve(URI rootUri, URI uri, boolean relative) { protected URI resolve(URI rootUri, URI uri, boolean relative) {
@ -442,8 +442,8 @@ public abstract class RrdBackendFactory implements Closeable {
/** /**
* Transform an path in a valid URI for this backend. * Transform an path in a valid URI for this backend.
* *
* @param path * @param path a path local to the current backend.
* @return * @return an URI that the current backend can handle.
*/ */
public URI getUri(String path) { public URI getUri(String path) {
URI rootUri = getRootUri(); URI rootUri = getRootUri();
@ -561,7 +561,7 @@ public abstract class RrdBackendFactory implements Closeable {
/** /**
* A generic close handle, default implementation does nothing. * A generic close handle, default implementation does nothing.
* @since 3.4 * @since 3.4
* @throws IOException * @throws IOException if the close fails
*/ */
public void close() throws IOException { public void close() throws IOException {

View File

@ -65,7 +65,7 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
} }
/** /**
* Builds a {@link RrdDb} instance. * Builds or imports a {@link RrdDb} instance.
* *
* @return a new build RrdDb * @return a new build RrdDb
* @throws IOException in case of I/O error. * @throws IOException in case of I/O error.
@ -108,12 +108,12 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
/** /**
* Import an external rrd data, import definition must have been done using {@link #setExternalPath(String)} * Import an external rrd data, import definition must have been done using {@link #setExternalPath(String)}
* or {@link #setImporter(DataImporter)} * or {@link #setImporter(DataImporter)}.<p>
* It can be used when it's not need to keep a reference to the rrd.
* *
* @throws IOException in case of I/O error. * @throws IOException in case of I/O error.
* @throws IllegalArgumentException if the builder settings were incomplete * @throws IllegalArgumentException if the builder settings were incomplete
*/ */
@SuppressWarnings("deprecation")
public void doimport() throws IOException { public void doimport() throws IOException {
if (rrdDef != null || (importer == null && externalPath == null)) { if (rrdDef != null || (importer == null && externalPath == null)) {
throw new IllegalArgumentException("Not an importing configuration"); throw new IllegalArgumentException("Not an importing configuration");
@ -128,8 +128,8 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
} }
try (DataImporter rrdImporter = resoleImporter(externalPath, importer)) { try (DataImporter rrdImporter = resoleImporter(externalPath, importer)) {
if (usePool) { if (usePool) {
RrdDb db = resolvePool(pool).requestRrdDb(rrdUri, factory, importer); try (RrdDb db = resolvePool(pool).requestRrdDb(rrdUri, factory, importer)) {
resolvePool(pool).release(db); };
} else { } else {
try (RrdDb db = new RrdDb(path, rrdUri, null, rrdImporter, factory, null)) { try (RrdDb db = new RrdDb(path, rrdUri, null, rrdImporter, factory, null)) {
} }
@ -151,16 +151,29 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
return this; return this;
} }
/**
* @param factory The backend factory to use for that rrd.
* @return the same builder.
*/
public Builder setBackendFactory(RrdBackendFactory factory) { public Builder setBackendFactory(RrdBackendFactory factory) {
this.factory = factory; this.factory = factory;
return this; return this;
} }
/**
* @param readOnly true if the rrd is to be read only
* @return the same builder.
*/
public Builder setReadOnly(boolean readOnly) { public Builder setReadOnly(boolean readOnly) {
this.readOnly = readOnly; this.readOnly = readOnly;
return this; return this;
} }
/**
* Set the rrd as readonly
*
* @return the same builder.
*/
public Builder readOnly() { public Builder readOnly() {
this.readOnly = true; this.readOnly = true;
return this; return this;
@ -174,7 +187,7 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
/** /**
* Activate the pool usage * Activate the pool usage
* *
* @return * @return the same builder.
*/ */
public Builder usePool() { public Builder usePool() {
this.usePool = true; this.usePool = true;
@ -185,14 +198,19 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
* Set the pool that will be used if {@link #usePool} is true. If not defined, * Set the pool that will be used if {@link #usePool} is true. If not defined,
* the singleton instance will be used. * the singleton instance will be used.
* *
* @param pool * @param pool true if a pool is going to be used
* @return * @return the same builder.
*/ */
public Builder setPool(RrdDbPool pool) { public Builder setPool(RrdDbPool pool) {
this.pool = pool; this.pool = pool;
return this; return this;
} }
/**
* Set when the builder will be used to import external data with a predefined source: XML or RRDTool.
* @param externalPath an URI-like indication of RRD data to import
* @return the same builder.
*/
public Builder setExternalPath(String externalPath) { public Builder setExternalPath(String externalPath) {
this.externalPath = externalPath; this.externalPath = externalPath;
this.importer = null; this.importer = null;
@ -201,6 +219,11 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
return this; return this;
} }
/**
* Set when the builder will be used to import external data with a custom source.
* @param importer a custom import
* @return the same builder.
*/
public Builder setImporter(DataImporter importer) { public Builder setImporter(DataImporter importer) {
this.importer = importer; this.importer = importer;
this.externalPath = null; this.externalPath = null;
@ -209,6 +232,12 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
return this; return this;
} }
/**
* Set when the builder will be used to import a RRDTool file.
* @param externalPath the path to a RRDTool file
* @return the same builder.
* @throws IOException if the RRDTool file cant be read
*/
public Builder setRrdToolImporter(String externalPath) throws IOException { public Builder setRrdToolImporter(String externalPath) throws IOException {
this.importer = new RrdToolReader(externalPath); this.importer = new RrdToolReader(externalPath);
this.externalPath = null; this.externalPath = null;
@ -217,6 +246,10 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
return this; return this;
} }
/**
* @param rrdDef a {@link RrdDef} to a new rrd file.
* @return the same builder.
*/
public Builder setRrdDef(RrdDef rrdDef) { public Builder setRrdDef(RrdDef rrdDef) {
this.rrdDef = rrdDef; this.rrdDef = rrdDef;
this.importer = null; this.importer = null;
@ -439,6 +472,32 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
} }
} }
/**
* <p>Opens an existing RRD with read/write access.
* The path will be parsed as an URI and checked against the active factories.
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
*
* @param path Path to existing RRD.
* @return a {link RrdDb} opened with default settings
* @throws java.io.IOException Thrown in case of I/O error.
*/
public static RrdDb of(String path) throws IOException {
return new RrdDb(path, null, false, null, null);
}
/**
* <p>Opens an existing RRD with read/write access.
* The URI will checked against the active factories.
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
*
* @param uri URI to existing RRD.
* @return a {link RrdDb} opened with default settings
* @throws java.io.IOException Thrown in case of I/O error.
*/
public static RrdDb of(URI uri) throws IOException {
return new RrdDb(null, uri, false, null, null);
}
/** /**
* <p>Constructor used to open already existing RRD. The path will be parsed as an URI and checked against the active factories. If * <p>Constructor used to open already existing RRD. The path will be parsed as an URI and checked against the active factories. If
* it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p> * it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
@ -513,18 +572,6 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
this(path, null, false, null, null); this(path, null, false, null, null);
} }
/**
* <p>Opens an existing RRD with read/write access.
* The path will be parsed as an URI and checked against the active factories.
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
*
* @param path Path to existing RRD.
* @throws java.io.IOException Thrown in case of I/O error.
*/
public static RrdDb of(String path) throws IOException {
return new RrdDb(path, null, false, null, null);
}
/** /**
* <p>Constructor used to open already existing RRD. The URI will checked against the active factories. If * <p>Constructor used to open already existing RRD. The URI will checked against the active factories. If
* it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p> * it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
@ -539,18 +586,6 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
this(null, uri, false, null, null); this(null, uri, false, null, null);
} }
/**
* <p>Opens an existing RRD with read/write access.
* The URI will checked against the active factories.
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
*
* @param uri URI to existing RRD.
* @throws java.io.IOException Thrown in case of I/O error.
*/
public static RrdDb of(URI uri) throws IOException {
return new RrdDb(null, uri, false, null, null);
}
/** /**
* Constructor used to open already existing RRD in R/W mode with a storage (backend) type * Constructor used to open already existing RRD in R/W mode with a storage (backend) type
* different from default. * different from default.

View File

@ -1,15 +1,21 @@
package org.rrd4j.core; package org.rrd4j.core;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.URI; import java.net.URI;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** /**
* <p>This class should be used to synchronize access to RRD files * <p>This class should be used to synchronize access to RRD files
@ -25,6 +31,12 @@ public class RrdDbPool {
private RrdDbPoolSingletonHolder() {} private RrdDbPoolSingletonHolder() {}
} }
private static class PoolFullException extends RuntimeException {
PoolFullException() {
super("", null, false, false);
}
}
/** /**
* Initial capacity of the pool i.e. maximum number of simultaneously open RRD files. The pool will * Initial capacity of the pool i.e. maximum number of simultaneously open RRD files. The pool will
* never open too many RRD files at the same time. * never open too many RRD files at the same time.
@ -33,10 +45,10 @@ public class RrdDbPool {
/* /*
* The RrdEntry stored in the pool can be of tree kind: * The RrdEntry stored in the pool can be of tree kind:
* - null, the URI is available, just for it and play * - null, the URI is available, just take it and play
* - placeholder is true, it's not the real RrdDb entry, just a place holder * - placeholder is true, it's not the real RrdDb entry, just a place holder
* meaning that some other thread is using it. * meaning that some other thread is using it. Wait until the real entry is put back.
* - placehold is false, this is the real entry pointing to a RrdDb. It's * - placeholder is false, this is the active entry pointing to a RrdDb. It's
* only used by the current thread. * only used by the current thread.
* *
*/ */
@ -44,29 +56,33 @@ public class RrdDbPool {
RrdDb rrdDb = null; RrdDb rrdDb = null;
int count = 0; int count = 0;
final CountDownLatch waitempty; final CountDownLatch waitempty;
final CountDownLatch inuse; final ReentrantReadWriteLock inuse;
final Lock lock;
final boolean placeholder; final boolean placeholder;
final URI uri; final URI uri;
RrdEntry(boolean placeholder, URI canonicalPath) { RrdEntry(URI canonicalPath) throws InterruptedException {
this.placeholder = placeholder; placeholder = false;
this.uri = canonicalPath; uri = canonicalPath;
if (placeholder) { inuse = new ReentrantReadWriteLock();
inuse = new CountDownLatch(1); lock = inuse.writeLock();
waitempty = null; waitempty = new CountDownLatch(1);
} else { }
inuse = null; RrdEntry(RrdEntry parent) throws InterruptedException {
waitempty = new CountDownLatch(1); assert ! parent.placeholder;
} placeholder = true;
uri = parent.uri;
inuse = null;
lock = parent.inuse.readLock();
waitempty = null;
} }
@Override @Override
public String toString() { public String toString() {
if (this.placeholder) { if (placeholder) {
return "RrdEntry [inuse=" + inuse.getCount()+ ", uri=" + uri + "]"; return String.format("RrdEntry [placeholder, uri=%s]", uri);
} else { } else {
return "RrdEntry [rrdDb=" + rrdDb + ", count=" + count + ", uri=" + uri + "]"; return String.format("RrdEntry [count=%d, rrdDb=%s, uri%s]", count, rrdDb, uri);
} }
} }
} }
/** /**
@ -80,21 +96,37 @@ public class RrdDbPool {
return RrdDbPoolSingletonHolder.instance; return RrdDbPoolSingletonHolder.instance;
} }
private final AtomicInteger usage = new AtomicInteger(0);
private final ReentrantLock countLock = new ReentrantLock();
private final Condition full = countLock.newCondition();
private int maxCapacity = INITIAL_CAPACITY; private int maxCapacity = INITIAL_CAPACITY;
private Semaphore usage = new Semaphore(maxCapacity);
private final ReentrantReadWriteLock.WriteLock usageWLock;
private final ReentrantReadWriteLock.ReadLock usageRLock;
private final Condition fullCondition;
// Needed because external threads can detect waiting condition
private final AtomicBoolean waitFull = new AtomicBoolean(false);
private final ConcurrentMap<URI, RrdEntry> pool = new ConcurrentHashMap<>(INITIAL_CAPACITY); private final ConcurrentMap<URI, RrdEntry> pool = new ConcurrentHashMap<>(INITIAL_CAPACITY);
private final RrdBackendFactory defaultFactory; private RrdBackendFactory defaultFactory;
/** /**
* Constructor for RrdDbPool. * Constructor for RrdDbPool.
* @since 3.5 * @since 3.5
*/ */
public RrdDbPool() { public RrdDbPool() {
defaultFactory = RrdBackendFactory.getDefaultFactory(); this(RrdBackendFactory.getDefaultFactory());
}
/**
* Constructor for RrdDbPool.
* @param defaultFactory the default factory used when given simple path of a rrdDb.
* @since 3.6
*/
public RrdDbPool(RrdBackendFactory defaultFactory) {
this.defaultFactory = defaultFactory;
ReentrantReadWriteLock usageLock = new ReentrantReadWriteLock(true);
usageWLock = usageLock.writeLock();
usageRLock = usageLock.readLock();
fullCondition = usageWLock.newCondition();
} }
/** /**
@ -103,7 +135,7 @@ public class RrdDbPool {
* @return Number of currently open RRD files held in the pool. * @return Number of currently open RRD files held in the pool.
*/ */
public int getOpenFileCount() { public int getOpenFileCount() {
return usage.get(); return pool.size();
} }
/** /**
@ -113,9 +145,9 @@ public class RrdDbPool {
*/ */
public URI[] getOpenUri() { public URI[] getOpenUri() {
//Direct toarray from keySet can fail //Direct toarray from keySet can fail
Set<URI> files = new HashSet<>(); Set<URI> uris = new HashSet<>(pool.size());
files.addAll(pool.keySet()); pool.forEach((k,v) -> uris.add(k));
return files.toArray(new URI[files.size()]); return uris.toArray(new URI[uris.size()]);
} }
/** /**
@ -125,53 +157,84 @@ public class RrdDbPool {
*/ */
public String[] getOpenFiles() { public String[] getOpenFiles() {
//Direct toarray from keySet can fail //Direct toarray from keySet can fail
Set<String> files = new HashSet<>(); Set<String> uris = new HashSet<>(pool.size());
for (RrdEntry i: pool.values()) { pool.forEach((k,v) -> uris.add(k.getPath()));
files.add(i.rrdDb.getPath()); return uris.toArray(new String[uris.size()]);
}
return files.toArray(new String[files.size()]);
} }
private RrdEntry getEntry(URI uri, boolean cancreate) throws InterruptedException { private RrdEntry getEntry(URI uri, boolean cancreate) throws InterruptedException {
RrdEntry ref = null; RrdEntry ref = null;
try { try {
CompletableFuture<RrdEntry> holder = new CompletableFuture<>();
do { do {
ref = pool.get(uri); try {
if (ref == null) { ref = pool.compute(uri, (u, e) -> {
//Slot empty try {
//If still absent put a place holder, and create the entry to return if (e == null) {
try { if (cancreate) {
countLock.lockInterruptibly(); usageRLock.lockInterruptibly();
while (ref == null && usage.get() >= maxCapacity && cancreate) { try {
full.await(); if (! usage.tryAcquire()) {
ref = pool.get(uri); throw new PoolFullException();
} } else {
if (ref == null && cancreate) { RrdEntry r = new RrdEntry(u);
ref = pool.putIfAbsent(uri, new RrdEntry(true, uri)); holder.complete(r);
if (ref == null) { r.lock.lock();
ref = new RrdEntry(false, uri); return new RrdEntry(r);
usage.incrementAndGet(); }
} finally {
usageRLock.unlock();
}
} else {
throw new IllegalStateException("Unknown URI in pool: " + u);
}
} else {
if (e.placeholder) {
return e;
} else {
e.lock.lock();
holder.complete(e);
return new RrdEntry(e);
}
} }
} catch (InterruptedException ex) {
holder.completeExceptionally(ex);
return null;
} }
});
} catch (PoolFullException e) {
ref = null;
try {
usageWLock.lockInterruptibly();
waitFull.set(true);
fullCondition.await();
} catch (InterruptedException ex) {
holder.completeExceptionally(ex);
Thread.currentThread().interrupt();
} finally { } finally {
countLock.unlock(); if (usageWLock.isHeldByCurrentThread()) {
waitFull.set(false);
usageWLock.unlock();
}
} }
} else if (! ref.placeholder) {
// Real entry, try to put a place holder if some one didn't get it meanwhile
if ( ! pool.replace(uri, ref, new RrdEntry(true, uri))) {
//Dummy ref, a new iteration is needed
ref = new RrdEntry(true, uri);
}
} else {
// a place holder, wait for the using task to finish
ref.inuse.await();
} }
} while (ref != null && ref.placeholder); if (ref != null && !holder.isDone()) {
return ref; // Wait for a signal from the active entry, it's available
ref.lock.lockInterruptibly();
ref.lock.unlock();
}
} while (! holder.isDone());
return holder.get();
} catch (ExecutionException e) {
InterruptedException ex = (InterruptedException) e.getCause();
Thread.currentThread().interrupt();
throw ex;
} catch (InterruptedException | RuntimeException e) { } catch (InterruptedException | RuntimeException e) {
// Oups we were interrupted, put everything back and go away // Oups we were interrupted, put everything back and go away
passNext(ACTION.SWAP, ref); passNext(ACTION.SWAP, ref);
Thread.currentThread().interrupt(); if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw e; throw e;
} }
} }
@ -191,21 +254,26 @@ public class RrdDbPool {
break; break;
case DROP: case DROP:
o = pool.remove(e.uri); o = pool.remove(e.uri);
if(usage.decrementAndGet() < maxCapacity) { usage.release();
assert o == null || o.placeholder;
if (waitFull.get()) {
try { try {
countLock.lockInterruptibly(); usageWLock.lockInterruptibly();
full.signalAll(); fullCondition.signalAll();
countLock.unlock();
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
Thread.currentThread().interrupt(); throw new UndeclaredThrowableException(ex);
} finally {
if (usageWLock.isHeldByCurrentThread()) {
usageWLock.unlock();
}
} }
} }
break; break;
} }
assert o != e : String.format("Same entry, action=%s, entry=%s\n", a, e);
assert o == null || ((e.placeholder && ! o.placeholder) || (o.placeholder && ! e.placeholder)) : String.format("Inconsistent entry, action=%s, in=%s out=%s\n", a, e, o);
//task finished, waiting on a place holder can go on //task finished, waiting on a place holder can go on
if(o != null) { e.lock.unlock();
o.inuse.countDown();
}
} }
/** /**
@ -214,11 +282,12 @@ public class RrdDbPool {
* *
* @param rrdDb RrdDb reference to be returned to the pool * @param rrdDb RrdDb reference to be returned to the pool
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated a pool remember if it was open directly or from the pool, no need to manage it manually any more * @deprecated a db remember if it was open directly or from the pool, no need to manage it manually any more
*/ */
@Deprecated @Deprecated
public void release(RrdDb rrdDb) throws IOException { public void release(RrdDb rrdDb) throws IOException {
// null pointer should not kill the thread, just ignore it // null pointer should not kill the thread, just ignore it
// They can happens in case of failures or interruptions at wrong place
if (rrdDb == null) { if (rrdDb == null) {
return; return;
} }
@ -229,23 +298,23 @@ public class RrdDbPool {
ref = getEntry(dburi, false); ref = getEntry(dburi, false);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new IllegalStateException("release interrupted for " + rrdDb, e); throw new IllegalStateException("Release interrupted for " + rrdDb.getPath(), e);
} }
if (ref == null) { if (ref == null) {
return; throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], not using pool for it");
}
if (ref.rrdDb == null) {
passNext(ACTION.DROP, ref);
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], pool corruption");
} }
if (ref.count <= 0) { if (ref.count <= 0) {
passNext(ACTION.DROP, ref); passNext(ACTION.DROP, ref);
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], the file was never requested"); throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], the file was never requested");
} }
if (--ref.count == 0) { if (--ref.count == 0) {
if(ref.rrdDb == null) {
passNext(ACTION.DROP, ref);
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], pool corruption");
}
try { try {
ref.rrdDb.internalClose(); ref.rrdDb.internalClose();
ref.rrdDb = null;
} finally { } finally {
passNext(ACTION.DROP, ref); passNext(ACTION.DROP, ref);
//If someone is waiting for an empty entry, signal it //If someone is waiting for an empty entry, signal it
@ -272,9 +341,7 @@ public class RrdDbPool {
* @param path Path to existing RRD file * @param path Path to existing RRD file
* @return reference for the give RRD file * @return reference for the give RRD file
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
*/ */
@Deprecated
public RrdDb requestRrdDb(String path) throws IOException { public RrdDb requestRrdDb(String path) throws IOException {
return requestRrdDb(defaultFactory.getUri(path), defaultFactory); return requestRrdDb(defaultFactory.getUri(path), defaultFactory);
} }
@ -293,38 +360,12 @@ public class RrdDbPool {
* @param uri {@link URI} to existing RRD file * @param uri {@link URI} to existing RRD file
* @return reference for the give RRD file * @return reference for the give RRD file
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
*/ */
@Deprecated
public RrdDb requestRrdDb(URI uri) throws IOException { public RrdDb requestRrdDb(URI uri) throws IOException {
RrdBackendFactory factory = RrdBackendFactory.findFactory(uri); RrdBackendFactory factory = RrdBackendFactory.findFactory(uri);
return requestRrdDb(uri, factory); return requestRrdDb(uri, factory);
} }
RrdDb requestRrdDb(URI uri, RrdBackendFactory factory) throws IOException {
uri = factory.getCanonicalUri(uri);
RrdEntry ref = null;
try {
ref = getEntry(uri, true);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("request interrupted for " + uri, e);
}
//Someone might have already open it, rechecks
if (ref.count == 0) {
try {
ref.rrdDb = RrdDb.getBuilder().setPath(factory.getPath(uri)).setBackendFactory(factory).setPool(this).build();
} catch (IOException | RuntimeException e) {
passNext(ACTION.DROP, ref);
throw e;
}
}
ref.count++;
passNext(ACTION.SWAP, ref);
return ref.rrdDb;
}
/** /**
* Wait for a empty reference with no usage * Wait for a empty reference with no usage
* @param uri * @param uri
@ -359,10 +400,78 @@ public class RrdDbPool {
*/ */
private RrdEntry requestEmpty(URI uri) throws InterruptedException, IOException { private RrdEntry requestEmpty(URI uri) throws InterruptedException, IOException {
RrdEntry ref = waitEmpty(uri); RrdEntry ref = waitEmpty(uri);
ref.count = 1;
return ref; return ref;
} }
RrdDb requestRrdDb(URI uri, RrdBackendFactory factory) throws IOException {
uri = factory.getCanonicalUri(uri);
RrdEntry ref = null;
try {
ref = getEntry(uri, true);
// Someone might have already open it, rechecks
if (ref.count == 0) {
try {
ref.rrdDb = RrdDb.getBuilder().setPath(factory.getPath(uri)).setBackendFactory(factory).setPool(this).build();
} catch (IOException | RuntimeException e) {
passNext(ACTION.DROP, ref);
throw e;
}
}
ref.count++;
passNext(ACTION.SWAP, ref);
return ref.rrdDb;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("request interrupted for " + uri, e);
}
}
RrdDb requestRrdDb(RrdDef rrdDef, RrdBackendFactory backend) throws IOException {
RrdEntry ref = null;
try {
URI uri = backend.getCanonicalUri(rrdDef.getUri());
ref = requestEmpty(uri);
ref.rrdDb = RrdDb.getBuilder().setRrdDef(rrdDef).setBackendFactory(backend).setPool(this).build();
ref.count = 1;
return ref.rrdDb;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("request interrupted for new rrdDef " + rrdDef.getPath(), e);
} catch (RuntimeException e) {
passNext(ACTION.DROP, ref);
ref = null;
throw e;
} finally {
passNext(ACTION.SWAP, ref);
}
}
private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory backend)
throws IOException {
RrdEntry ref = null;
uri = backend.getCanonicalUri(uri);
try {
ref = requestEmpty(uri);
ref.rrdDb = builder.setPath(uri).setBackendFactory(backend).setPool(this).build();
ref.count = 1;
return ref.rrdDb;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("request interrupted for new rrd " + uri, e);
} catch (RuntimeException e) {
passNext(ACTION.DROP, ref);
ref = null;
throw e;
} finally {
passNext(ACTION.SWAP, ref);
}
}
RrdDb requestRrdDb(URI uri, RrdBackendFactory backend, DataImporter importer) throws IOException {
return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, backend);
}
/** /**
* <p>Requests a RrdDb reference for the given RRD file definition object.</p> * <p>Requests a RrdDb reference for the given RRD file definition object.</p>
* <ul> * <ul>
@ -377,34 +486,11 @@ public class RrdDbPool {
* @param rrdDef Definition of the RRD file to be created * @param rrdDef Definition of the RRD file to be created
* @return Reference to the newly created RRD file * @return Reference to the newly created RRD file
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
*/ */
@Deprecated
public RrdDb requestRrdDb(RrdDef rrdDef) throws IOException { public RrdDb requestRrdDb(RrdDef rrdDef) throws IOException {
return requestRrdDb(rrdDef, RrdBackendFactory.findFactory(rrdDef.getUri())); return requestRrdDb(rrdDef, RrdBackendFactory.findFactory(rrdDef.getUri()));
} }
RrdDb requestRrdDb(RrdDef rrdDef, RrdBackendFactory backend) throws IOException {
RrdEntry ref = null;
try {
URI uri = backend.getCanonicalUri(rrdDef.getUri());
ref = requestEmpty(uri);
ref.rrdDb = RrdDb.getBuilder().setRrdDef(rrdDef).setBackendFactory(backend).setPool(this).build();
return ref.rrdDb;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("request interrupted for new rrdDef " + rrdDef.getPath(), e);
} catch (RuntimeException e) {
passNext(ACTION.DROP, ref);
ref = null;
throw e;
} finally {
if (ref != null) {
passNext(ACTION.SWAP, ref);
}
}
}
/** /**
* <p>Requests a RrdDb reference for the given path. The file will be created from * <p>Requests a RrdDb reference for the given path. The file will be created from
* external data (from XML dump or RRDTool's binary RRD file).</p> * external data (from XML dump or RRDTool's binary RRD file).</p>
@ -416,21 +502,17 @@ public class RrdDbPool {
* If the file is not already open and the number of already open RRD files is equal to * If the file is not already open and the number of already open RRD files is equal to
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
* </ul> * </ul>
* <p>The path is transformed internally to URI using the default factory, that is the reference that will * <p>The path is transformed internally to an URI using the default factory of the pool.</p>
* be used elsewhere.</p>
* *
* @param path Path to RRD file which should be created * @param path Path to RRD file which should be created
* @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format * @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format
* @return Reference to the newly created RRD file * @return Reference to the newly created RRD file
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
*/ */
@Deprecated
public RrdDb requestRrdDb(String path, String sourcePath) public RrdDb requestRrdDb(String path, String sourcePath)
throws IOException { throws IOException {
URI uri = RrdBackendFactory.getDefaultFactory().getUri(path); URI uri = defaultFactory.getUri(path);
RrdBackendFactory backend = RrdBackendFactory.getDefaultFactory(); return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, defaultFactory);
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, backend);
} }
/** /**
@ -451,56 +533,57 @@ public class RrdDbPool {
* @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format * @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format
* @return Reference to the newly created RRD file * @return Reference to the newly created RRD file
* @throws java.io.IOException Thrown in case of I/O error * @throws java.io.IOException Thrown in case of I/O error
* @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
*/ */
@Deprecated
public RrdDb requestRrdDb(URI uri, String sourcePath) public RrdDb requestRrdDb(URI uri, String sourcePath)
throws IOException { throws IOException {
RrdBackendFactory backend = RrdBackendFactory.getDefaultFactory(); return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, RrdBackendFactory.findFactory(uri));
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, backend);
} }
private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory backend) /**
throws IOException { * Sets the default factory to use when obtaining rrdDb from simple path and not URI.
RrdEntry ref = null; *
uri = backend.getCanonicalUri(uri); * @param defaultFactory The factory to used.
* @throws IllegalStateException if done will the pool is not empty or the thread was interrupted.
*/
public void setDefaultFactory(RrdBackendFactory defaultFactory) {
try { try {
ref = requestEmpty(uri); usageWLock.lockInterruptibly();
ref.rrdDb = builder.setPath(uri).setBackendFactory(backend).setPool(this).build(); if (usage.availablePermits() != maxCapacity) {
return ref.rrdDb; throw new IllegalStateException("Can only be done on a empty pool");
}
this.defaultFactory = defaultFactory;
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RuntimeException("request interrupted for new rrd " + uri, e); throw new IllegalStateException("Factory not changed");
} catch (RuntimeException e) {
passNext(ACTION.DROP, ref);
ref = null;
throw e;
} finally { } finally {
if (ref != null) { if (usageWLock.isHeldByCurrentThread()) {
passNext(ACTION.SWAP, ref); usageWLock.unlock();
} }
} }
} }
RrdDb requestRrdDb(URI uri, RrdBackendFactory backend, DataImporter importer) throws IOException {
return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, backend);
}
/** /**
* Sets the maximum number of simultaneously open RRD files. * Sets the maximum number of simultaneously open RRD files.
* *
* @param newCapacity Maximum number of simultaneously open RRD files. * @param newCapacity Maximum number of simultaneously open RRD files.
* @throws IllegalStateException if done will the pool is not empty or the thread was interrupted.
*/ */
public void setCapacity(int newCapacity) { public void setCapacity(int newCapacity) {
int oldUsage = usage.getAndSet(maxCapacity);
try { try {
if (oldUsage != 0) { usageWLock.lockInterruptibly();
throw new RuntimeException("Can only be done on a empty pool"); if (usage.availablePermits() != maxCapacity) {
throw new IllegalStateException("Can only be done on a empty pool");
} }
maxCapacity = newCapacity;
usage = new Semaphore(maxCapacity);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Resizing interrupted");
} finally { } finally {
usage.set(oldUsage); if (usageWLock.isHeldByCurrentThread()) {
usageWLock.unlock();
}
} }
maxCapacity = newCapacity;
} }
/** /**
@ -509,7 +592,17 @@ public class RrdDbPool {
* @return maximum number of simultaneously open RRD files * @return maximum number of simultaneously open RRD files
*/ */
public int getCapacity() { public int getCapacity() {
return maxCapacity; try {
usageRLock.lockInterruptibly();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted, can't get pool size");
}
try {
return maxCapacity;
} finally {
usageRLock.unlock();
}
} }
/** /**
@ -545,18 +638,12 @@ public class RrdDbPool {
RrdEntry ref = null; RrdEntry ref = null;
try { try {
ref = getEntry(uri, false); ref = getEntry(uri, false);
if (ref == null) return Optional.ofNullable(ref).map(e -> e.count).orElse(0);
return 0;
else {
return ref.count;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RuntimeException("getOpenCount interrupted", e); throw new RuntimeException("getOpenCount interrupted", e);
} finally { } finally {
if (ref != null) { passNext(ACTION.SWAP, ref);
passNext(ACTION.SWAP, ref);
}
} }
} }

View File

@ -44,7 +44,7 @@ public class RrdDef {
* Default RRD step to be used if not specified in constructor (300 seconds). * Default RRD step to be used if not specified in constructor (300 seconds).
*/ */
public static final long DEFAULT_STEP = 300L; public static final long DEFAULT_STEP = 300L;
/** /**
* If not specified in constructor, starting timestamp will be set to the * If not specified in constructor, starting timestamp will be set to the
* current timestamp plus DEFAULT_INITIAL_SHIFT seconds (-10). * current timestamp plus DEFAULT_INITIAL_SHIFT seconds (-10).
@ -631,37 +631,37 @@ public class RrdDef {
* @param compatible Compatible with previous versions. * @param compatible Compatible with previous versions.
*/ */
public void exportXmlTemplate(OutputStream out, boolean compatible) { public void exportXmlTemplate(OutputStream out, boolean compatible) {
XmlWriter xml = new XmlWriter(out); try (XmlWriter xml = new XmlWriter(out)) {
xml.startTag("rrd_def"); xml.startTag("rrd_def");
if (compatible) { if (compatible) {
xml.writeTag("path", getPath()); xml.writeTag("path", getPath());
} else { } else {
xml.writeTag("uri", getUri()); xml.writeTag("uri", getUri());
}
xml.writeTag("step", getStep());
xml.writeTag("start", getStartTime());
// datasources
DsDef[] dsDefs = getDsDefs();
for (DsDef dsDef : dsDefs) {
xml.startTag("datasource");
xml.writeTag("name", dsDef.getDsName());
xml.writeTag("type", dsDef.getDsType());
xml.writeTag("heartbeat", dsDef.getHeartbeat());
xml.writeTag("min", dsDef.getMinValue(), "U");
xml.writeTag("max", dsDef.getMaxValue(), "U");
xml.closeTag(); // datasource
}
ArcDef[] arcDefs = getArcDefs();
for (ArcDef arcDef : arcDefs) {
xml.startTag("archive");
xml.writeTag("cf", arcDef.getConsolFun());
xml.writeTag("xff", arcDef.getXff());
xml.writeTag("steps", arcDef.getSteps());
xml.writeTag("rows", arcDef.getRows());
xml.closeTag(); // archive
}
xml.closeTag(); // rrd_def
} }
xml.writeTag("step", getStep());
xml.writeTag("start", getStartTime());
// datasources
DsDef[] dsDefs = getDsDefs();
for (DsDef dsDef : dsDefs) {
xml.startTag("datasource");
xml.writeTag("name", dsDef.getDsName());
xml.writeTag("type", dsDef.getDsType());
xml.writeTag("heartbeat", dsDef.getHeartbeat());
xml.writeTag("min", dsDef.getMinValue(), "U");
xml.writeTag("max", dsDef.getMaxValue(), "U");
xml.closeTag(); // datasource
}
ArcDef[] arcDefs = getArcDefs();
for (ArcDef arcDef : arcDefs) {
xml.startTag("archive");
xml.writeTag("cf", arcDef.getConsolFun());
xml.writeTag("xff", arcDef.getXff());
xml.writeTag("steps", arcDef.getSteps());
xml.writeTag("rows", arcDef.getRows());
xml.closeTag(); // archive
}
xml.closeTag(); // rrd_def
xml.flush();
} }
/** /**

View File

@ -1,8 +1,11 @@
package org.rrd4j.core; package org.rrd4j.core;
import java.io.File; import java.io.IOError;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/** /**
* An abstract backend factory which is used to store RRD data to ordinary files on the disk. * An abstract backend factory which is used to store RRD data to ordinary files on the disk.
@ -20,7 +23,7 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
*/ */
@Override @Override
protected boolean exists(String path) { protected boolean exists(String path) {
return Util.fileExists(path); return Files.exists(Paths.get(path));
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -37,25 +40,27 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
@Override @Override
public URI getCanonicalUri(URI uri) { public URI getCanonicalUri(URI uri) {
// Resolve only parent, to avoid failing if the file is missing
Path file;
try { try {
if (uri.isOpaque()) { if (uri.isOpaque() || uri.getScheme() == null) {
return new File(uri.getSchemeSpecificPart()).getCanonicalFile().toURI(); file = Paths.get(uri.getSchemeSpecificPart());
} else if (uri.isAbsolute()) {
return new File(uri).getCanonicalFile().toURI();
} else { } else {
return new File(uri.getPath()).getCanonicalFile().toURI(); file = Paths.get(uri);
} }
} catch (IOException e) { Path parent = file.getParent().toRealPath();
throw new IllegalArgumentException("can't get canonical URI from " + uri + ": " + e); return parent.resolve(file.getFileName()).toUri();
} catch (IOError | IOException e) {
throw new IllegalArgumentException("can't get canonical URI from " + uri + ": " + e, e);
} }
} }
@Override @Override
public URI getUri(String path) { public URI getUri(String path) {
try { try {
return new File(path).getCanonicalFile().toURI(); return Paths.get(path).normalize().toUri();
} catch (IOException e) { } catch (IOError e) {
throw new IllegalArgumentException("can't get canonical URI from path " + path + ": " + e); throw new IllegalArgumentException("can't get URI from path " + path + ": " + e, e);
} }
} }
@ -64,7 +69,7 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
if (uri.isOpaque()) { if (uri.isOpaque()) {
return uri.getSchemeSpecificPart(); return uri.getSchemeSpecificPart();
} else if (uri.isAbsolute()) { } else if (uri.isAbsolute()) {
return new File(uri).getPath(); return Paths.get(uri).normalize().toString();
} else { } else {
return uri.getPath(); return uri.getPath();
} }

View File

@ -90,9 +90,9 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
} }
/** /**
* Creates a new RrdNioBackendFactory. * Creates a new RrdNioBackendFactory, using a default {@link RrdSyncThreadPool}.
* *
* @param syncPeriod If syncPeriod is negative or 0, sync threads are disabled. * @param syncPeriod the sync period, in seconds. If negative or 0, sync threads are disabled.
*/ */
public RrdNioBackendFactory(int syncPeriod) { public RrdNioBackendFactory(int syncPeriod) {
this(syncPeriod, syncPeriod > 0 ? DefaultSyncThreadPool.INSTANCE : null); this(syncPeriod, syncPeriod > 0 ? DefaultSyncThreadPool.INSTANCE : null);
@ -101,8 +101,8 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
/** /**
* Creates a new RrdNioBackendFactory. * Creates a new RrdNioBackendFactory.
* *
* @param syncPeriod * @param syncPeriod the sync period, in seconds.
* @param syncPoolSize The number of threads to use to sync the mapped file to disk, if inferior to 0, sync threads are disabled. * @param syncPoolSize The number of threads to use to sync the mapped file to disk, if negative or 0, sync threads are disabled.
*/ */
public RrdNioBackendFactory(int syncPeriod, int syncPoolSize) { public RrdNioBackendFactory(int syncPeriod, int syncPoolSize) {
this(syncPeriod, syncPoolSize > 0 ? new RrdSyncThreadPool(syncPoolSize) : null); this(syncPeriod, syncPoolSize > 0 ? new RrdSyncThreadPool(syncPoolSize) : null);
@ -111,7 +111,7 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
/** /**
* Creates a new RrdNioBackendFactory. * Creates a new RrdNioBackendFactory.
* *
* @param syncPeriod * @param syncPeriod the sync period, in seconds.
* @param syncThreadPool If null, disable background sync threads * @param syncThreadPool If null, disable background sync threads
*/ */
public RrdNioBackendFactory(int syncPeriod, ScheduledExecutorService syncThreadPool) { public RrdNioBackendFactory(int syncPeriod, ScheduledExecutorService syncThreadPool) {
@ -121,7 +121,7 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
/** /**
* Creates a new RrdNioBackendFactory. * Creates a new RrdNioBackendFactory.
* *
* @param syncPeriod * @param syncPeriod the sync period, in seconds.
* @param syncThreadPool If null, disable background sync threads * @param syncThreadPool If null, disable background sync threads
*/ */
public RrdNioBackendFactory(int syncPeriod, RrdSyncThreadPool syncThreadPool) { public RrdNioBackendFactory(int syncPeriod, RrdSyncThreadPool syncThreadPool) {

View File

@ -34,8 +34,8 @@ public class RrdSafeFileBackendFactory extends RrdRandomAccessFileBackendFactory
/** /**
* Generate a factory with custom lock settings * Generate a factory with custom lock settings
* @param lockWaitTime * @param lockWaitTime wait time in ms
* @param lockRetryPeriod * @param lockRetryPeriod retry period in ms
*/ */
public RrdSafeFileBackendFactory(long lockWaitTime, long lockRetryPeriod) { public RrdSafeFileBackendFactory(long lockWaitTime, long lockRetryPeriod) {
this.lockWaitTime = lockWaitTime; this.lockWaitTime = lockWaitTime;

View File

@ -1,18 +1,18 @@
package org.rrd4j.core; package org.rrd4j.core;
import org.rrd4j.ConsolFun;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.rrd4j.ConsolFun;
/** /**
* Class used to perform various complex operations on RRD files. Use an instance of the * Class used to perform various complex operations on RRD files. Use an instance of the
* RrdToolkit class to: * RrdToolkit class to:
@ -323,28 +323,14 @@ public class RrdToolkit {
private static void copyFile(String sourcePath, String destPath, boolean saveBackup) private static void copyFile(String sourcePath, String destPath, boolean saveBackup)
throws IOException { throws IOException {
File source = new File(sourcePath);
File dest = new File(destPath); Path source = Paths.get(sourcePath);
Path destination = Paths.get(destPath);
if (saveBackup) { if (saveBackup) {
String backupPath = getBackupPath(destPath); String backupPath = getBackupPath(destPath);
File backup = new File(backupPath); Files.move(destination, Paths.get(backupPath), StandardCopyOption.REPLACE_EXISTING);
deleteFile(backup);
if (!dest.renameTo(backup)) {
throw new RrdException("Could not create backup file " + backupPath);
}
}
deleteFile(dest);
if (!source.renameTo(dest)) {
//Rename failed so try to copy and erase
try(FileChannel sourceStream = new FileInputStream(source).getChannel(); FileChannel destinationStream = new FileOutputStream(dest).getChannel()) {
long count = 0;
final long size = sourceStream.size();
while(count < size) {
count += destinationStream.transferFrom(sourceStream, count, size-count);
}
deleteFile(source);
}
} }
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
} }
private static String getBackupPath(String destPath) { private static String getBackupPath(String destPath) {
@ -515,12 +501,6 @@ public class RrdToolkit {
copyFile(destPath, sourcePath, saveBackup); copyFile(destPath, sourcePath, saveBackup);
} }
private static void deleteFile(File file) throws IOException {
if (file.exists()) {
Files.delete(file.toPath());
}
}
/** /**
* Splits single RRD file with several datasources into a number of smaller RRD files * Splits single RRD file with several datasources into a number of smaller RRD files
* with a single datasource in it. All archived values are preserved. If * with a single datasource in it. All archived values are preserved. If

View File

@ -29,6 +29,7 @@ import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -236,6 +237,7 @@ public class Util {
/** /**
* Returns timestamp (unix epoch) for the given year, month, day, hour and minute. * Returns timestamp (unix epoch) for the given year, month, day, hour and minute.
* <p>The date is resolved in the current time zone</p>
* *
* @param year Year * @param year Year
* @param month Month (zero-based) * @param month Month (zero-based)
@ -253,6 +255,7 @@ public class Util {
/** /**
* Returns timestamp (unix epoch) for the given year, month and day. * Returns timestamp (unix epoch) for the given year, month and day.
* <p>The date is resolved in the current time zone</p>
* *
* @param year Year * @param year Year
* @param month Month (zero-based) * @param month Month (zero-based)
@ -403,8 +406,8 @@ public class Util {
root = Paths.get(getUserHomeDirectory(), RRD4J_DIR); root = Paths.get(getUserHomeDirectory(), RRD4J_DIR);
} }
try { try {
Files.createDirectories(root); root = Files.createDirectories(root.toAbsolutePath().normalize());
return root.toAbsolutePath().toString() + File.separator; return root.toString() + File.separator;
} catch (IOException e) { } catch (IOException e) {
return null; return null;
} }
@ -731,7 +734,7 @@ public class Util {
* @throws java.io.IOException Thrown if canonical file path could not be resolved * @throws java.io.IOException Thrown if canonical file path could not be resolved
*/ */
public static String getCanonicalPath(String path) throws IOException { public static String getCanonicalPath(String path) throws IOException {
return new File(path).getCanonicalPath(); return Paths.get(path).toRealPath().toString();
} }
/** /**
@ -739,9 +742,27 @@ public class Util {
* *
* @param file File object representing file on the disk * @param file File object representing file on the disk
* @return Last modification time in seconds (without milliseconds) * @return Last modification time in seconds (without milliseconds)
* @deprecated use #getLastModifiedTime, that can throws exceptions if needed
*/ */
@Deprecated
public static long getLastModified(String file) { public static long getLastModified(String file) {
return (new File(file).lastModified() + 500L) / 1000L; try {
return Files.getLastModifiedTime(Paths.get(file)).to(TimeUnit.SECONDS);
} catch (IOException e) {
// For compatibility with old API
return 0;
}
}
/**
* Returns last modification time for the given file.
*
* @param file File object representing file on the disk
* @return Last modification time in seconds (without milliseconds)
* @throws IOException
*/
public static long getLastModifiedTime(String file) throws IOException {
return Files.getLastModifiedTime(Paths.get(file)).to(TimeUnit.SECONDS);
} }
/** /**
@ -751,7 +772,7 @@ public class Util {
* @return <code>true</code> if file exists, <code>false</code> otherwise * @return <code>true</code> if file exists, <code>false</code> otherwise
*/ */
public static boolean fileExists(String filename) { public static boolean fileExists(String filename) {
return new File(filename).exists(); return Files.exists(Paths.get(filename));
} }
/** /**

View File

@ -15,17 +15,13 @@ import java.util.TimeZone;
/** /**
* Extremely simple utility class used to create XML documents. * Extremely simple utility class used to create XML documents.
*/ */
public class XmlWriter { public class XmlWriter implements AutoCloseable {
static final String INDENT_STR = " "; static final String INDENT_STR = " ";
private static final String STYLE = "style"; private static final String STYLE = "style";
private static final ThreadLocal<SimpleDateFormat> ISOLIKE = new ThreadLocal<SimpleDateFormat>() { private static final SimpleDateFormat ISOLIKE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH);
@Override static {
protected SimpleDateFormat initialValue() { ISOLIKE.setTimeZone(TimeZone.getTimeZone("UTC"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH); }
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf;
}
};
private final PrintWriter writer; private final PrintWriter writer;
private final StringBuilder indent = new StringBuilder(""); private final StringBuilder indent = new StringBuilder("");
@ -198,7 +194,9 @@ public class XmlWriter {
*/ */
public void writeComment(Object comment) { public void writeComment(Object comment) {
if (comment instanceof Date) { if (comment instanceof Date) {
comment = ISOLIKE.get().format((Date) comment); synchronized (ISOLIKE) {
comment = ISOLIKE.format((Date) comment);
}
} }
writer.println(indent + "<!-- " + escape(comment.toString()) + " -->"); writer.println(indent + "<!-- " + escape(comment.toString()) + " -->");
} }
@ -207,4 +205,10 @@ public class XmlWriter {
return s.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); return s.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
} }
@Override
public void close() {
writer.flush();
writer.close();
}
} }

View File

@ -35,33 +35,34 @@ import java.util.Date;
* *
*/ */
public class Epoch extends JFrame { public class Epoch extends JFrame {
private static final String[] supportedFormats = { private final String[] supportedFormats = {
"MM/dd/yy HH:mm:ss", "dd.MM.yy HH:mm:ss", "yy-MM-dd HH:mm:ss", "MM/dd/yy HH:mm", "MM/dd/yy HH:mm:ss", "dd.MM.yy HH:mm:ss", "yy-MM-dd HH:mm:ss", "MM/dd/yy HH:mm",
"dd.MM.yy HH:mm", "yy-MM-dd HH:mm", "MM/dd/yy", "dd.MM.yy", "yy-MM-dd", "HH:mm MM/dd/yy", "dd.MM.yy HH:mm", "yy-MM-dd HH:mm", "MM/dd/yy", "dd.MM.yy", "yy-MM-dd", "HH:mm MM/dd/yy",
"HH:mm dd.MM.yy", "HH:mm yy-MM-dd", "HH:mm:ss MM/dd/yy", "HH:mm:ss dd.MM.yy", "HH:mm:ss yy-MM-dd" "HH:mm dd.MM.yy", "HH:mm yy-MM-dd", "HH:mm:ss MM/dd/yy", "HH:mm:ss dd.MM.yy", "HH:mm:ss yy-MM-dd"
}; };
@SuppressWarnings("unchecked") private final SimpleDateFormat[] parsers = new SimpleDateFormat[supportedFormats.length];
private static final ThreadLocal<SimpleDateFormat>[] parsers = new ThreadLocal[supportedFormats.length]; private final String helpText;
private static final String helpText;
private Timer timer = new Timer(1000, new ActionListener() { private final Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
showTimestamp(); showTimestamp();
} }
}); });
static { private final JLabel topLabel = new JLabel("Enter timestamp or readable date:");
private final JTextField inputField = new JTextField(25);
private final JButton convertButton = new JButton("Convert");
private final JButton helpButton = new JButton("Help");
private final SimpleDateFormat OUTPUT_DATE_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss EEE");
private Epoch() {
super("Epoch");
for (int i = 0; i < parsers.length; i++) { for (int i = 0; i < parsers.length; i++) {
final String format = supportedFormats[i]; String format = supportedFormats[i];
parsers[i] = new ThreadLocal<SimpleDateFormat>() { parsers[i] = new SimpleDateFormat(format);
@Override parsers[i].setLenient(true);
protected SimpleDateFormat initialValue() {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setLenient(true);
return sdf;
}
};
} }
StringBuilder tooltipBuff = new StringBuilder("<html><b>Supported input formats:</b><br>"); StringBuilder tooltipBuff = new StringBuilder("<html><b>Supported input formats:</b><br>");
for (String supportedFormat : supportedFormats) { for (String supportedFormat : supportedFormats) {
@ -69,24 +70,8 @@ public class Epoch extends JFrame {
} }
tooltipBuff.append("<b>AT-style time specification</b><br>"); tooltipBuff.append("<b>AT-style time specification</b><br>");
tooltipBuff.append("timestamp<br><br>"); tooltipBuff.append("timestamp<br><br>");
tooltipBuff.append("Copyright (c) 2013 The RRD4J Authors. Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2013 The OpenNMS Group, Inc. Licensed under the Apache License, Version 2.0.</html>"); tooltipBuff.append("Copyright (c) 2013-2020 The RRD4J Authors. Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2013 The OpenNMS Group, Inc. Licensed under the Apache License, Version 2.0.</html>");
helpText = tooltipBuff.toString(); helpText = tooltipBuff.toString();
}
private JLabel topLabel = new JLabel("Enter timestamp or readable date:");
private JTextField inputField = new JTextField(25);
private JButton convertButton = new JButton("Convert");
private JButton helpButton = new JButton("Help");
private static final ThreadLocal<SimpleDateFormat> OUTPUT_DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MM/dd/yy HH:mm:ss EEE");
}
};
Epoch() {
super("Epoch");
constructUI(); constructUI();
timer.start(); timer.start();
} }
@ -118,7 +103,7 @@ public class Epoch extends JFrame {
setVisible(true); setVisible(true);
} }
void centerOnScreen() { private void centerOnScreen() {
Toolkit t = Toolkit.getDefaultToolkit(); Toolkit t = Toolkit.getDefaultToolkit();
Dimension screenSize = t.getScreenSize(); Dimension screenSize = t.getScreenSize();
Dimension frameSize = getPreferredSize(); Dimension frameSize = getPreferredSize();
@ -153,14 +138,14 @@ public class Epoch extends JFrame {
setTitle(timestamp + " seconds since epoch"); setTitle(timestamp + " seconds since epoch");
} }
void formatDate(Date date) { private synchronized void formatDate(Date date) {
inputField.setText(OUTPUT_DATE_FORMAT.get().format(date)); inputField.setText(OUTPUT_DATE_FORMAT.format(date));
} }
private long parseDate(String time) { private synchronized long parseDate(String time) {
for (ThreadLocal<SimpleDateFormat> parser : parsers) { for (SimpleDateFormat parser : parsers) {
try { try {
return Util.getTimestamp(parser.get().parse(time)); return Util.getTimestamp(parser.parse(time));
} }
catch (ParseException e) { catch (ParseException e) {
} }

View File

@ -148,7 +148,7 @@ public class DataProcessor {
/** /**
* Defines the {@link org.rrd4j.core.RrdDbPool RrdDbPool} to use. If not defined, but {{@link #setPoolUsed(boolean)} * Defines the {@link org.rrd4j.core.RrdDbPool RrdDbPool} to use. If not defined, but {{@link #setPoolUsed(boolean)}
* set to true, the default {@link RrdDbPool#getInstance()} will be used. * set to true, the default {@link RrdDbPool#getInstance()} will be used.
* @param pool * @param pool an optional pool to use.
*/ */
public void setPool(RrdDbPool pool) { public void setPool(RrdDbPool pool) {
this.pool = pool; this.pool = pool;

View File

@ -3,17 +3,14 @@ package org.rrd4j.graph;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.Stroke; import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.font.LineMetrics; import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -28,8 +25,7 @@ import javax.imageio.stream.ImageOutputStream;
class ImageWorker { class ImageWorker {
private static final String DUMMY_TEXT = "Dummy"; private static final String DUMMY_TEXT = "Dummy";
private static final int IMG_BUFFER_CAPACITY = 10000; // bytes
static final int IMG_BUFFER_CAPACITY = 10000; // bytes
private BufferedImage img; private BufferedImage img;
private Graphics2D g2d; private Graphics2D g2d;
@ -224,16 +220,9 @@ class ImageWorker {
} }
} }
/** void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
* <p>loadImage.</p> BufferedImage wpImage = imageSource.apply(w, h).getSubimage(0, 0, w, h);
* g2d.drawImage(wpImage, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
* @param imageFile a {@link java.lang.String} object.
* @throws java.io.IOException if any.
*/
public void loadImage(String imageFile) throws IOException {
BufferedImage wpImage = ImageIO.read(new File(imageFile));
TexturePaint paint = new TexturePaint(wpImage, new Rectangle(0, 0, wpImage.getWidth(), wpImage.getHeight()));
g2d.setPaint(paint);
g2d.fillRect(0, 0, wpImage.getWidth(), wpImage.getHeight());
} }
} }

View File

@ -1,8 +1,5 @@
package org.rrd4j.graph; package org.rrd4j.graph;
import org.rrd4j.graph.ImageParameters;
import org.rrd4j.graph.RrdGraphDef;
class Mapper { class Mapper {
private final RrdGraphDef gdef; private final RrdGraphDef gdef;
private final ImageParameters im; private final ImageParameters im;

View File

@ -175,7 +175,7 @@ public class RrdGraph implements RrdGraphConstants {
private void drawOverlay() throws IOException { private void drawOverlay() throws IOException {
if (gdef.overlayImage != null) { if (gdef.overlayImage != null) {
worker.loadImage(gdef.overlayImage); worker.loadImage(gdef.overlayImage, 0, 0, im.xgif, im.ygif);
} }
} }
@ -393,7 +393,10 @@ public class RrdGraph implements RrdGraphConstants {
private void drawBackground() throws IOException { private void drawBackground() throws IOException {
worker.fillRect(0, 0, im.xgif, im.ygif, gdef.getColor(ElementsNames.back)); worker.fillRect(0, 0, im.xgif, im.ygif, gdef.getColor(ElementsNames.back));
if (gdef.backgroundImage != null) { if (gdef.backgroundImage != null) {
worker.loadImage(gdef.backgroundImage); worker.loadImage(gdef.backgroundImage, 0, 0, im.xgif, im.ygif);
}
if (gdef.canvasImage != null) {
worker.loadImage(gdef.canvasImage, im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize);
} }
worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.getColor(ElementsNames.canvas)); worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.getColor(ElementsNames.canvas));
} }
@ -666,14 +669,14 @@ public class RrdGraph implements RrdGraphConstants {
im.end = gdef.endTime; im.end = gdef.endTime;
} }
private boolean lazyCheck() { private boolean lazyCheck() throws IOException {
// redraw if lazy option is not set or file does not exist // redraw if lazy option is not set or file does not exist
if (!gdef.lazy || !Util.fileExists(gdef.filename)) { if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
return false; // 'false' means 'redraw' return false; // 'false' means 'redraw'
} }
// redraw if not enough time has passed // redraw if not enough time has passed
long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width; long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width;
long elapsed = Util.getTimestamp() - Util.getLastModified(gdef.filename); long elapsed = Util.getTimestamp() - Util.getLastModifiedTime(gdef.filename);
return elapsed <= secPerPixel; return elapsed <= secPerPixel;
} }

View File

@ -4,12 +4,18 @@ import java.awt.BasicStroke;
import java.awt.Font; import java.awt.Font;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Stroke; import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import javax.imageio.ImageIO;
import org.rrd4j.ConsolFun; import org.rrd4j.ConsolFun;
import org.rrd4j.core.FetchData; import org.rrd4j.core.FetchData;
import org.rrd4j.core.RrdBackendFactory; import org.rrd4j.core.RrdBackendFactory;
@ -47,6 +53,49 @@ import org.rrd4j.data.Variable;
* the string to disable the auto justification.</p> * the string to disable the auto justification.</p>
*/ */
public class RrdGraphDef implements RrdGraphConstants { public class RrdGraphDef implements RrdGraphConstants {
/**
* <p>Implementations of this class can be used to generate image than can be
* layered on graph. The can be used for background image, a background image
* draw on canvas or an overlay image.</p>
* @author Fabrice Bacchella
*
*/
public interface ImageSource {
/**
* A image of the required size that will be applied. If the generated image is too big, it will be clipped before being applied.
* @param w the width of the requested image
* @param h the high of the requested image
* @return an image to draw.
* @throws IOException
*/
BufferedImage apply(int w, int h) throws IOException;
}
private static class FileImageSource implements ImageSource {
private final File imagesource;
FileImageSource(String imagesource) {
this.imagesource = new File(imagesource);
}
public BufferedImage apply(int w, int h) throws IOException {
return ImageIO.read(imagesource);
}
}
private static class UrlImageSource implements ImageSource {
private final URL imagesource;
private UrlImageSource(URL imagesource) {
this.imagesource = imagesource;
}
public BufferedImage apply(int w, int h) throws IOException {
return ImageIO.read(imagesource);
}
}
boolean poolUsed = false; // ok boolean poolUsed = false; // ok
boolean antiAliasing = false; // ok boolean antiAliasing = false; // ok
boolean textAntiAliasing = false; // ok boolean textAntiAliasing = false; // ok
@ -69,8 +118,9 @@ public class RrdGraphDef implements RrdGraphConstants {
String imageInfo = null; // ok String imageInfo = null; // ok
String imageFormat = DEFAULT_IMAGE_FORMAT; // ok String imageFormat = DEFAULT_IMAGE_FORMAT; // ok
float imageQuality = DEFAULT_IMAGE_QUALITY; // ok float imageQuality = DEFAULT_IMAGE_QUALITY; // ok
String backgroundImage = null; // ok ImageSource backgroundImage = null; // ok
String overlayImage = null; // ok ImageSource canvasImage = null; // ok
ImageSource overlayImage = null; // ok
String unit = null; // ok String unit = null; // ok
boolean lazy = false; // ok boolean lazy = false; // ok
double minValue = Double.NaN; // ok double minValue = Double.NaN; // ok
@ -466,6 +516,7 @@ public class RrdGraphDef implements RrdGraphConstants {
/** /**
* Sets image format. * Sets image format.
* ImageIO is used to save the image, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
* *
* @param imageFormat Any value as return by {@link javax.imageio.ImageIO#getReaderFormatNames} * @param imageFormat Any value as return by {@link javax.imageio.ImageIO#getReaderFormatNames}
*/ */
@ -474,22 +525,90 @@ public class RrdGraphDef implements RrdGraphConstants {
} }
/** /**
* Sets background image - currently, only PNG images can be used as background. * Sets background image.
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
* *
* @param backgroundImage Path to background image * @param backgroundImage Path to background image
*/ */
public void setBackgroundImage(String backgroundImage) { public void setBackgroundImage(String backgroundImage) {
this.backgroundImage = new FileImageSource(backgroundImage);
}
/**
* Sets background image.
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
*
* @param backgroundImageUrl URL to background image
*/
public void setBackgroundImage(URL backgroundImageUrl) {
this.backgroundImage = new UrlImageSource(backgroundImageUrl);
}
/**
* Sets background image.
*
* @param backgroundImage An {@link ImageSource} that will provides a {@link BufferedImage}
*/
public void setBackgroundImage(ImageSource backgroundImage) {
this.backgroundImage = backgroundImage; this.backgroundImage = backgroundImage;
} }
/** /**
* Sets overlay image - currently, only PNG images can be used as overlay. Overlay image is * Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
* printed on the top of the image, once it is completely created. * ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
*
* @param canvasImage Path to canvas image
*/
public void setCanvasImage(String canvasImage) {
this.canvasImage = new FileImageSource(canvasImage);
}
/**
* Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
*
* @param canvasUrl URL to canvas image
*/
public void setCanvasImage(URL canvasUrl) {
this.canvasImage = new UrlImageSource(canvasUrl);
}
/**
* Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
*
* @param canvasImageSource An {@link ImageSource} that will provides a {@link BufferedImage}
*/
public void setCanvasImage(ImageSource canvasImageSource) {
this.canvasImage = canvasImageSource;
}
/**
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
* *
* @param overlayImage Path to overlay image * @param overlayImage Path to overlay image
*/ */
public void setOverlayImage(String overlayImage) { public void setOverlayImage(String overlayImage) {
this.overlayImage = overlayImage; this.overlayImage = new FileImageSource(overlayImage);
}
/**
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
*
* @param overlayImage URL to overlay image
*/
public void setOverlayImage(URL overlayImage) {
this.overlayImage = new UrlImageSource(overlayImage);
}
/**
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
*
* @param overlayImageSource An {@link ImageSource} that will provides a {@link BufferedImage}
*/
public void setOverlayImage(ImageSource overlayImageSource) {
this.overlayImage = overlayImageSource;
} }
/** /**
@ -598,8 +717,8 @@ public class RrdGraphDef implements RrdGraphConstants {
/** /**
* Overrides the colors for the standard elements of the graph. * Overrides the colors for the standard elements of the graph.
* @param colorTag * @param colorTag The element to change color.
* @param color * @param color The color of the element.
*/ */
public void setColor(ElementsNames colorTag, Paint color) { public void setColor(ElementsNames colorTag, Paint color) {
colors[colorTag.ordinal()] = color; colors[colorTag.ordinal()] = color;