Skip to content

Commit

Permalink
#198 implement reading routes file from classpath (not only from loca…
Browse files Browse the repository at this point in the history
…l file)

it allows users to extract route files to modules (a jar file added to the project as a dependency).
  • Loading branch information
asolntsev committed Jan 21, 2024
1 parent ba9d5a2 commit 62d5f15
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 36 deletions.
55 changes: 55 additions & 0 deletions framework/src/play/ClasspathResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package play;

import org.apache.commons.io.IOUtils;
import play.exceptions.UnexpectedException;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.regex.Pattern;

import static java.nio.charset.StandardCharsets.UTF_8;

public class ClasspathResource {
private static final Pattern RE_JAR_FILE_NAME = Pattern.compile("file:(.+\\.jar)!.+");
private final URL url;

public static ClasspathResource file(String fileName) {
return new ClasspathResource(Thread.currentThread().getContextClassLoader().getResource(fileName));
}

ClasspathResource(URL url) {
this.url = url;
}

@Override
public String toString() {
return "File " + url;
}

public String content() {
try {
return IOUtils.toString(url, UTF_8);
} catch (IOException e) {
throw new UnexpectedException(e);
}
}

public boolean isModifiedAfter(long timestamp) {
switch (url.getProtocol()) {
case "file": {
return new File(url.getFile()).lastModified() > timestamp;
}
case "jar": {
return new File(getJarFilePath()).lastModified() > timestamp;
}
default: {
return false;
}
}
}

String getJarFilePath() {
return RE_JAR_FILE_NAME.matcher(url.getPath()).replaceFirst("$1");
}
}
14 changes: 5 additions & 9 deletions framework/src/play/Play.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import play.inject.DefaultBeanSource;
import play.inject.Injector;
import play.jobs.Job;
import play.libs.IO;
import play.mvc.ActionInvoker;
import play.mvc.CookieSessionStore;
import play.mvc.PlayController;
Expand All @@ -29,12 +28,11 @@
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import static java.nio.charset.StandardCharsets.UTF_8;
import static play.ClasspathResource.file;

/**
* Main framework class
Expand Down Expand Up @@ -78,9 +76,9 @@ public boolean isProd() {
*/
public static List<VirtualFile> templatesPath = new ArrayList<>(2);
/**
* Main routes file
* The routes file
*/
public static VirtualFile routes;
public static ClasspathResource routes;

/**
* The app configuration (already resolved from the framework id)
Expand Down Expand Up @@ -148,11 +146,9 @@ public void init(String id) {
logger.info("Starting {}", applicationPath.getAbsolutePath());
setupTmpDir();
setupApplicationMode();
VirtualFile appRoot = setupAppRoot();
routes = appRoot.child("conf/routes");

setupAppRoot();
routes = file("routes");
pluginCollection.loadPlugins();

Play.invoker = new Invoker();
}

Expand Down
10 changes: 0 additions & 10 deletions framework/src/play/libs/IO.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package play.libs;

import org.apache.commons.io.FileUtils;
import play.exceptions.UnexpectedException;
import play.utils.OrderSafeProperties;
import play.vfs.VirtualFile;
Expand All @@ -9,7 +8,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Properties;

public class IO {
Expand Down Expand Up @@ -42,12 +40,4 @@ public static Properties readUtf8Properties(InputStream is) throws IOException {
properties.load(is);
return properties;
}

public static String readContentAsString(File file, Charset encoding) {
try {
return FileUtils.readFileToString(file, encoding);
} catch (IOException e) {
throw new UnexpectedException(e);
}
}
}
11 changes: 6 additions & 5 deletions framework/src/play/mvc/Router.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package play.mvc;

import java.util.Map.Entry;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.ClasspathResource;
import play.Play;
import play.Play.Mode;
import play.exceptions.NoRouteFoundException;
Expand All @@ -18,6 +17,7 @@
import play.utils.Utils;
import play.vfs.VirtualFile;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
Expand All @@ -28,6 +28,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -133,7 +134,7 @@ public static void detectChanges() {
if (Play.routes == null) {
return;
}
if (Play.routes.lastModified() > lastLoading) {
if (Play.routes.isModifiedAfter(lastLoading)) {
loadRoutesFromFile();
}
}
Expand Down Expand Up @@ -580,13 +581,13 @@ public static class Route {
final Pattern pattern;
final List<Arg> args = new ArrayList<>(3);
final Map<String, String> staticArgs = new HashMap<>(3);
public final String routesFile;
public final ClasspathResource routesFile;
public final int routesFileLine;

private static final Pattern customRegexPattern = Pattern.compile("\\{([a-zA-Z_][a-zA-Z_0-9]*)}");
private static final Pattern argsPattern = Pattern.compile("\\{<([^>]+)>([a-zA-Z_0-9]+)}");

public Route(String method, String path, String action, String sourceFile, int line) {
public Route(String method, String path, String action, ClasspathResource sourceFile, int line) {
this.method = method;
this.path = path;
this.action = action;
Expand Down
23 changes: 11 additions & 12 deletions framework/src/play/mvc/routing/RoutesParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.ClasspathResource;
import play.mvc.Router.Route;
import play.vfs.VirtualFile;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -18,22 +18,21 @@ public class RoutesParser {
* Parse a route file. If an action starts with <i>"plugin:name"</i>, replace that route by the ones declared in the
* plugin route file denoted by that <i>name</i>, if found.
*/
public List<Route> parse(VirtualFile routeFile) {
String fileAbsolutePath = routeFile.getRealFile().getAbsolutePath();
String content = routeFile.contentAsString();
assertDoesNotContain(fileAbsolutePath, content, "${");
assertDoesNotContain(fileAbsolutePath, content, "#{");
assertDoesNotContain(fileAbsolutePath, content, "%{");
return parse(content, fileAbsolutePath);
public List<Route> parse(ClasspathResource routesFile) {
String content = routesFile.content();
assertDoesNotContain(routesFile, content, "${");
assertDoesNotContain(routesFile, content, "#{");
assertDoesNotContain(routesFile, content, "%{");
return parse(content, routesFile);
}

private void assertDoesNotContain(String fileAbsolutePath, String content, String substring) {
private void assertDoesNotContain(ClasspathResource routesFile, String content, String substring) {
if (content.contains(substring)) {
throw new IllegalArgumentException("Routes file " + fileAbsolutePath + " cannot contain " + substring);
throw new IllegalArgumentException("Routes file " + routesFile + " cannot contain " + substring);
}
}

private List<Route> parse(String content, String fileAbsolutePath) {
private List<Route> parse(String content, ClasspathResource routesFile) {
List<Route> routes = new ArrayList<>();

int lineNumber = 0;
Expand All @@ -48,7 +47,7 @@ private List<Route> parse(String content, String fileAbsolutePath) {
if (route.action.startsWith("module:")) {
throw new IllegalArgumentException(String.format("Modules are not supported anymore (found route '%s')", route.action));
} else {
routes.add(new Route(route.method, route.path.replace("//", "/"), route.action, fileAbsolutePath, lineNumber));
routes.add(new Route(route.method, route.path.replace("//", "/"), route.action, routesFile, lineNumber));
}
}
catch (IllegalArgumentException invalidRoute) {
Expand Down
16 changes: 16 additions & 0 deletions framework/test/play/ClasspathResourceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package play;

import org.junit.jupiter.api.Test;

import java.net.MalformedURLException;
import java.net.URL;

import static org.assertj.core.api.Assertions.assertThat;

class ClasspathResourceTest {
@Test
void extractsNameOfJarFile_from_classpathURL() throws MalformedURLException {
assertThat(new ClasspathResource(new URL("jar:file:/Users/toomas/replay/build/libs/app-1.2.3.jar!/routes.yml")).getJarFilePath())
.isEqualTo("/Users/toomas/replay/build/libs/app-1.2.3.jar");
}
}

0 comments on commit 62d5f15

Please sign in to comment.