Skip to content

Commit

Permalink
Refactored resource components.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmihajlovski committed Jan 18, 2016
1 parent faa9823 commit df9917a
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 142 deletions.
12 changes: 5 additions & 7 deletions rapidoid-commons/src/main/java/org/rapidoid/config/Conf.java
@@ -1,12 +1,12 @@
package org.rapidoid.config;

import java.util.Map;

import org.rapidoid.data.YAML;
import org.rapidoid.io.Res;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

import java.util.Map;

/*
* #%L
* rapidoid-commons
Expand Down Expand Up @@ -289,14 +289,12 @@ public static Config refreshing(String path, String filename) {

public static Config refreshing(String path, final String filename, final ConfigParser parser) {
Log.info("Initializing auto-refreshing config", "root", Conf.rootPath(), "path", path, "filename", filename);
path = U.safe(path);

final String firstFile = U.path(Conf.rootPath(), path, filename);
String defaultFile = U.path(Conf.rootPathDefault(), path, filename);
final String resPath = U.path(Conf.rootPath(), U.safe(path));

Log.info("Calculated resource path", "primary", firstFile, "default", defaultFile);
Log.info("Calculated resource path", "path", resPath);

final Res res = Res.from(filename, true, firstFile, defaultFile);
final Res res = Res.from(filename, null, resPath);

Config config = res.attachment();
if (config == null) {
Expand Down
106 changes: 64 additions & 42 deletions rapidoid-commons/src/main/java/org/rapidoid/io/Res.java
@@ -1,20 +1,19 @@
package org.rapidoid.io;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

import java.io.File;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

/*
* #%L
* rapidoid-commons
Expand All @@ -39,13 +38,15 @@
@Since("4.1.0")
public class Res {

private static final ConcurrentMap<Set<String>, Res> FILES = U.concurrentMap();
private static final ConcurrentMap<ResKey, Res> FILES = U.concurrentMap();

public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(1);

private final String shortName;
public static final String[] DEFAULT_LOCATIONS = {""};

private final String name;

private final Set<String> filenames;
private final String[] possibleLocations;

private volatile byte[] bytes;

Expand All @@ -63,37 +64,43 @@ public class Res {

private final Map<String, Runnable> changeListeners = U.synchronizedMap();

private Res(String shortName, Set<String> filenames) {
this.shortName = shortName;
this.filenames = filenames;
}

public static Res from(String filename) {
return from(filename, true, filename);
private Res(String name, String... possibleLocations) {
this.name = name;
this.possibleLocations = possibleLocations;
}

public static Res from(File file) {
return from(file.getAbsolutePath());
return file.isAbsolute() ? absolute(file) : from(file.getName(), "");
}

public static Res from(String shortName, boolean withDefaults, String... filenames) {
Set<String> fnames = U.set(filenames);
public static Res absolute(File file) {
U.must(file.isAbsolute(), "Expected from filename!");

if (withDefaults) {
for (String filename : filenames) {
fnames.add(IO.getDefaultFilename(filename));
}
return create(file.getAbsolutePath());
}

public static Res from(String filename, String... possibleLocations) {
if (U.isEmpty(possibleLocations)) {
possibleLocations = DEFAULT_LOCATIONS;
}

U.must(!fnames.isEmpty(), "Resource filename(s) must be specified!");
U.must(!U.isEmpty(filename), "Resource filename must be specified!");
U.must(!new File(filename).isAbsolute(), "Expected relative filename!");

return create(filename, possibleLocations);
}

private static Res create(String filename, String... possibleLocations) {
ResKey key = new ResKey(filename, possibleLocations);

Res cachedFile = FILES.get(fnames);
Res cachedFile = FILES.get(key);

if (cachedFile == null) {
cachedFile = new Res(shortName, fnames);
cachedFile = new Res(filename, possibleLocations);

if (FILES.size() < 1000) {
FILES.putIfAbsent(fnames, cachedFile);
// FIXME use real cache with proper expiration
FILES.putIfAbsent(key, cachedFile);
}
}

Expand Down Expand Up @@ -122,14 +129,29 @@ protected void loadResource() {
byte[] old = bytes;
byte[] foundRes = null;

for (String filename : filenames) {
Log.trace("Trying to load the resource", "name", shortName, "file", filename);
byte[] res = load(filename);
if (possibleLocations.length == 0) {
Log.trace("Trying to load the from resource", "name", name);

byte[] res = load(name);
if (res != null) {
Log.trace("Loaded the resource", "name", shortName, "file", filename);
Log.trace("Loaded the from resource", "name", name);
foundRes = res;
this.cachedFileName = filename;
break;
this.cachedFileName = name;
}

} else {
for (String location : possibleLocations) {
String filename = U.path(location, name);

Log.trace("Trying to load the resource", "name", name, "location", location, "filename", filename);

byte[] res = load(filename);
if (res != null) {
Log.trace("Loaded the resource", "name", name, "file", filename);
foundRes = res;
this.cachedFileName = filename;
break;
}
}
}

Expand Down Expand Up @@ -159,19 +181,19 @@ protected byte[] load(String filename) {
if (file.exists()) {

// a normal file on the file system
Log.trace("Resource file exists", "name", shortName, "file", file);
Log.trace("Resource file exists", "name", name, "file", file);

if (file.lastModified() > this.lastModified || !filename.equals(cachedFileName)) {
Log.info("Loading resource file", "name", shortName, "file", file);
Log.info("Loading resource file", "name", name, "file", file);
this.lastModified = file.lastModified();
return IO.loadBytes(filename);
} else {
Log.trace("Resource file not modified", "name", shortName, "file", file);
Log.trace("Resource file not modified", "name", name, "file", file);
return bytes;
}
} else {
// it might not exist or it might be on the classpath or compressed in a JAR
Log.trace("Trying to load classpath resource", "name", shortName, "file", file);
Log.trace("Trying to load classpath resource", "name", name, "file", file);
return IO.loadBytes(filename);
}
}
Expand All @@ -192,13 +214,13 @@ public boolean exists() {
return bytes != null;
}

public String getShortName() {
return shortName;
public String getName() {
return name;
}

@Override
public String toString() {
return "Res(" + shortName + ")";
return "Res(" + name + ")";
}

public synchronized Reader getReader() {
Expand All @@ -207,7 +229,7 @@ public synchronized Reader getReader() {
}

public void mustExist() {
U.must(exists(), "The file '%s' doesn't exist! Path: %s", shortName, filenames);
U.must(exists(), "The file '%s' doesn't exist! Path: %s", name, possibleLocations);
}

public Res onChange(Runnable listener) {
Expand All @@ -221,7 +243,7 @@ public Res onChange(String name, Runnable listener) {

private void notifyChangeListeners() {
if (!changeListeners.isEmpty()) {
Log.info("Resource has changed, reloading...", "name", shortName);
Log.info("Resource has changed, reloading...", "name", name);
}

for (Runnable listener : changeListeners.values()) {
Expand Down
39 changes: 39 additions & 0 deletions rapidoid-commons/src/main/java/org/rapidoid/io/ResKey.java
@@ -0,0 +1,39 @@
package org.rapidoid.io;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;

import java.util.Arrays;

@Authors("Nikolche Mihajlovski")
@Since("5.0.11")
public class ResKey {

final String filename;
final String[] possibleLocations;

public ResKey(String filename, String[] possibleLocations) {
this.filename = filename;
this.possibleLocations = possibleLocations;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

ResKey resKey = (ResKey) o;

if (!filename.equals(resKey.filename)) return false;

return Arrays.equals(possibleLocations, resKey.possibleLocations);
}

@Override
public int hashCode() {
int result = filename.hashCode();
result = 31 * result + Arrays.hashCode(possibleLocations);
return result;
}

}
30 changes: 15 additions & 15 deletions rapidoid-commons/src/test/java/org/rapidoid/io/ResTest.java
Expand Up @@ -30,54 +30,54 @@
public class ResTest extends AbstractCommonsTest {

@Test
public void testWhereDefaultsDontExist() {
Res file = Res.from("abc", true, "abc.html", "abc.txt", "abc.doc");
public void testWithSingleLocation() {
Res file = Res.from("abc.txt", "");

eq(file.getShortName(), "abc");
eq(file.getName(), "abc.txt");
isTrue(file.exists());
eq(file.getBytes(), "ABC!".getBytes());
}

@Test
public void testWhereDefaultsAlsoExist() {
Res file = Res.from("n-m", true, "nm.txt", "abc.txt", "abc.doc");
public void testWithMultipleLocations1() {
Res file = Res.from("abc.txt", "res1", "res2");

eq(file.getShortName(), "n-m");
eq(file.getName(), "abc.txt");
isTrue(file.exists());
eq(file.getContent(), "ABC!");
eq(file.getContent(), "ABC1");
}

@Test
public void testWhereOnlyDefaultsExist() {
Res file = Res.from("n-m2", true, "non-existing", "nm.txt", "non-existing2");
public void testWithMultipleLocations2() {
Res file = Res.from("abc.txt", "res1", "res2");

eq(file.getName(), "abc.txt");
isTrue(file.exists());
eq(file.getShortName(), "n-m2");
eq(file.getContent(), "NMDEF");
eq(file.getContent(), "ABC1");
}

// typically 1M reads should take less than a second
@Test(timeout = 5000)
public void shouldBeFast() {
for (int i = 0; i < 900; i++) {
// fill-in the cache (with non-existing resources)
Res.from("abc", true, "abc.txt" + i);
Res.from("abc", "x-location");
}

// should be fast
multiThreaded(100, 1000000, new Runnable() {
@Override
public void run() {
Res file = Res.from("ABC", true, "abc.txt");
Res file = Res.from("abc.txt", "");
notNull(file.getBytes());
}
});
}

@Test
public void testWithNonexistingFiles() {
Res file = Res.from("?", true, "some-non-existing-file");
eq(file.getShortName(), "?");
Res file = Res.from("some-non-existing-file", "res1", "", "res2");
eq(file.getName(), "some-non-existing-file");
isFalse(file.exists());
}

Expand Down
1 change: 1 addition & 0 deletions rapidoid-commons/src/test/resources/res1/abc.txt
@@ -0,0 +1 @@
ABC1
1 change: 1 addition & 0 deletions rapidoid-commons/src/test/resources/res2/abc.txt
@@ -0,0 +1 @@
ABC2
17 changes: 7 additions & 10 deletions rapidoid-quick/src/main/java/org/rapidoid/quick/Scripting.java
Expand Up @@ -20,13 +20,6 @@
* #L%
*/

import java.util.Map;
import java.util.Map.Entry;

import javax.script.CompiledScript;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.app.DollarPage;
Expand All @@ -47,6 +40,12 @@
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

import javax.script.CompiledScript;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import java.util.Map;
import java.util.Map.Entry;

@Authors("Nikolche Mihajlovski")
@Since("4.2.0")
public class Scripting {
Expand All @@ -73,10 +72,8 @@ private static CompiledScript script(ReqImpl x) {
+ HttpUtils.resName(x);

String filename = scriptName + ".js";
String firstFile = Conf.rootPath() + "/" + filename;
String defaultFile = Conf.rootPathDefault() + "/" + filename;
Res res = Res.from(filename, Conf.rootPath());

Res res = Res.from(filename, true, firstFile, defaultFile);
if (!res.exists()) {
return null;
}
Expand Down

0 comments on commit df9917a

Please sign in to comment.