Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion framework/pym/play/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import shutil
import socket
import glob

from play.utils import *

Expand Down Expand Up @@ -46,7 +47,7 @@ def __init__(self, application_path, env, ignoreMissingModules = False):

def check(self):
try:
assert os.path.exists(os.path.join(self.path, 'conf', 'routes'))
assert (os.path.exists(os.path.join(self.path, 'conf', 'routes')) or len(glob.glob(os.path.join(self.path, 'conf', 'routes.??')))>0 or len(glob.glob(os.path.join(self.path, 'conf', 'routes.??_??')))>0)
assert os.path.exists(os.path.join(self.path, 'conf', 'application.conf'))
except AssertionError:
print "~ Oops. conf/routes or conf/application.conf missing."
Expand Down
23 changes: 21 additions & 2 deletions framework/src/play/Play.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public boolean isProd() {
/**
* Main routes file
*/
public static VirtualFile routes;
public static List<VirtualFile> routes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks backward compatibility.
I suggest leaving VirtualFile routes untouched and adding a new field List<VirtualFile> internationalizedRoutes. In this case you don't need field multilangRouteFiles.

/**
* Plugin routes files
*/
Expand Down Expand Up @@ -182,6 +182,11 @@ public boolean isProd() {
*/
public static boolean standalonePlayServer = true;

/**
* This flag indicates that app has multiple routes files for different locales
*/
public static boolean multilangRouteFiles = false;

/**
* Init the framework
*
Expand Down Expand Up @@ -276,7 +281,7 @@ public static void init(File root, String id) {
}

// Main route file
routes = appRoot.child("conf/routes");
routes = loadRoutesFiles(appRoot);

// Plugin route files
modulesRoutes = new HashMap<>(16);
Expand Down Expand Up @@ -321,6 +326,20 @@ public static void init(File root, String id) {
Play.initialized = true;
}

public static List<VirtualFile> loadRoutesFiles(VirtualFile appRoot) {
List<VirtualFile> routes = new ArrayList<VirtualFile>();
for (VirtualFile vf: appRoot.child("conf").list()) {
String virtualFileName = vf.getName();
if(virtualFileName !=null && virtualFileName.equals("routes")){
routes.add(vf);
} else if(virtualFileName !=null && virtualFileName.matches("routes\\.[A-Za-z]{2}(_[A-Za-z]{2})?")){
routes.add(vf);
Play.multilangRouteFiles = true;
}
}
return routes;
}

public static void guessFrameworkPath() {
// Guess the framework path
try {
Expand Down
85 changes: 62 additions & 23 deletions framework/src/play/mvc/Router.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
package play.mvc;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.lang.StringUtils;

import jregex.Matcher;
import jregex.Pattern;
import jregex.REFlags;
import org.apache.commons.lang.StringUtils;
import play.Logger;
import play.Play;
import play.Play.Mode;
import play.exceptions.NoRouteFoundException;
import play.i18n.Lang;
import play.mvc.results.NotFound;
import play.mvc.results.RenderStatic;
import play.templates.TemplateLoader;
import play.utils.Default;
import play.utils.Utils;
import play.vfs.VirtualFile;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* The router matches HTTP requests to action invocations
*/
Expand All @@ -54,7 +51,9 @@ public class Router {
public static void load(String prefix) {
routes.clear();
actionRoutesCache.clear();
parse(Play.routes, prefix);
for (VirtualFile routeFile : Play.routes) {
parse(routeFile, prefix);
}
lastLoading = System.currentTimeMillis();
// Plugins
Play.pluginCollection.onRoutesLoaded();
Expand Down Expand Up @@ -140,6 +139,7 @@ public static Route getRoute(String method, String path, String action, String p
route.routesFileLine = line;
route.addFormat(headers);
route.addParams(params);
route.setLocaleBasedOnMultilangualRoutesFile(sourceFile);
route.compute();
if (Logger.isTraceEnabled()) {
Logger.trace("Adding [" + route.toString() + "] with params [" + params + "] and headers [" + headers + "]");
Expand Down Expand Up @@ -227,14 +227,16 @@ public static void detectChanges(String prefix) {
if (Play.mode == Mode.PROD && lastLoading > 0) {
return;
}
if (Play.routes.lastModified() > lastLoading) {
load(prefix);
} else {
for (VirtualFile file : Play.modulesRoutes.values()) {
if (file.lastModified() > lastLoading) {
load(prefix);
return;
}
for (VirtualFile route : Play.routes) {
if (route.lastModified() > lastLoading) {
load(prefix);
return;
}
}
for (VirtualFile file : Play.modulesRoutes.values()) {
if (file.lastModified() > lastLoading) {
load(prefix);
return;
}
}
}
Expand Down Expand Up @@ -292,6 +294,9 @@ public static Route route(Http.Request request) {
if (request.action.equals("404")) {
throw new NotFound(route.path);
}
if(Play.multilangRouteFiles && StringUtils.isNotEmpty(route.locale) && !route.locale.equals(Lang.get())){
Lang.change(route.locale);
}
return route;
}
}
Expand Down Expand Up @@ -588,6 +593,9 @@ private static List<ActionRoute> getActionRoutes(String action) {
matchingRoutes = findActionRoutes(action);
actionRoutesCache.put(action, matchingRoutes);
}
if(Play.multilangRouteFiles){
prioritizeActionRoutesBasedOnActiveLocale(matchingRoutes);
}
return matchingRoutes;
}

Expand All @@ -614,6 +622,26 @@ private static List<ActionRoute> findActionRoutes(String action) {
return matchingRoutes;
}

/**
* Prioritize action routes based on active locale and Play.langs properties. Active lang has highest priority, then prioritized according to Play.langs order.
*
* @param matchingRoutes
*/
private static void prioritizeActionRoutesBasedOnActiveLocale(List<Router.ActionRoute> matchingRoutes) {
if(matchingRoutes.size()==0) return;
final String locale = Lang.get();
if(StringUtils.isEmpty(locale)) return;
matchingRoutes.sort(new Comparator<ActionRoute>() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jenkins cannot compile the code

    [javac]         matchingRoutes.sort(new Comparator<ActionRoute>() {
    [javac]                       ^
    [javac]   symbol:   method sort(<anonymous Comparator<ActionRoute>>)
    [javac]   location: variable matchingRoutes of type List<ActionRoute>

Seems like some method are missing as you use the interface Comparator

Copy link
Author

@vadimv82 vadimv82 Jan 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I run same build on my machine with ant -buildfile ./framework/build.xml test
And build is succesfull.

I use anonymous comparator

sortedMatchingRoutes.sort(new Comparator() {
@OverRide
public int compare(ActionRoute ar1, ActionRoute ar2) {
if(locale.equals(ar1.route.locale)) return -1;
if(locale.equals(ar2.route.locale)) return 1;
return Integer.compare(Play.langs.indexOf(ar1.route.locale), Play.langs.indexOf(ar2.route.locale));
}
});

@Override
public int compare(ActionRoute ar1, ActionRoute ar2) {
if(locale.equals(ar1.route.locale)) return -1;
if(locale.equals(ar2.route.locale)) return 1;
return Integer.compare(Play.langs.indexOf(ar1.route.locale), Play.langs.indexOf(ar2.route.locale));
}
});
}


private static final class ActionRoute {
private Route route;
private Map<String, String> args = new HashMap<>(2);
Expand Down Expand Up @@ -732,6 +760,7 @@ public static class Route {
static Pattern customRegexPattern = new Pattern("\\{([a-zA-Z_][a-zA-Z_0-9]*)\\}");
static Pattern argsPattern = new Pattern("\\{<([^>]+)>([a-zA-Z_0-9]+)\\}");
static Pattern paramPattern = new Pattern("([a-zA-Z_0-9]+):'(.*)'");
String locale;

public void compute() {
this.host = "";
Expand Down Expand Up @@ -975,5 +1004,15 @@ static class Arg {
public String toString() {
return method + " " + path + " -> " + action;
}

private void setLocaleBasedOnMultilangualRoutesFile(String absolutePath){
if(StringUtils.isEmpty(absolutePath)){
return;
}
String fileName = Paths.get(absolutePath).getFileName().toString();
if(StringUtils.isNotEmpty(fileName) && fileName.matches("routes\\.[A-Za-z]{2}(_[A-Za-z]{2})?")){
this.locale = fileName.split("\\.")[1];
}
}
}
}
141 changes: 140 additions & 1 deletion framework/test-src/play/mvc/RouterTest.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package play.mvc;

import org.junit.Test;

import play.Play;
import play.i18n.Lang;
import play.mvc.Http.Request;
import play.mvc.results.NotFound;
import play.mvc.results.RenderStatic;
import play.vfs.VirtualFile;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RouterTest {

Expand Down Expand Up @@ -136,4 +142,137 @@ public boolean canRenderFile(Request request){
}
return false;
}

@Test
public void test_loadRoutesFiles() {
Play.multilangRouteFiles=false;

VirtualFile appRoot = mock(VirtualFile.class);
List<VirtualFile> routes = new ArrayList<>();
VirtualFile routesFile = mock(VirtualFile.class);
when(routesFile.getName()).thenReturn("routes");
routes.add(routesFile);
VirtualFile appConf = mock(VirtualFile.class);
when(appConf.getName()).thenReturn("application.conf");
routes.add(appConf);

VirtualFile confFolder = mock(VirtualFile.class);
when(confFolder.list()).thenReturn(routes);

when(appRoot.child("conf")).thenReturn(confFolder);

assertEquals(1,Play.loadRoutesFiles(appRoot).size());
assertEquals(false,Play.multilangRouteFiles);

routes = new ArrayList<>();
VirtualFile routesEnFile = mock(VirtualFile.class);
when(routesEnFile.getName()).thenReturn("routes.en");
routes.add(routesEnFile);
VirtualFile routesRuFile = mock(VirtualFile.class);
when(routesRuFile.getName()).thenReturn("routes.RU_ru");
routes.add(routesRuFile);
routes.add(appConf);
when(confFolder.list()).thenReturn(routes);

assertEquals(2,Play.loadRoutesFiles(appRoot).size());
assertEquals(true,Play.multilangRouteFiles);

}

@Test
public void test_detectNoChanges() {
long now = System.currentTimeMillis();

Router.lastLoading = now;

List<VirtualFile> routes = new ArrayList<>();
VirtualFile routesNotModifiedFile = mock(VirtualFile.class);
when(routesNotModifiedFile.getName()).thenReturn("routes.en");
when(routesNotModifiedFile.lastModified()).thenReturn(now-1000);
routes.add(routesNotModifiedFile);

VirtualFile routesNotModifiedFile1 = mock(VirtualFile.class);
when(routesNotModifiedFile1.getName()).thenReturn("routes.ru_RU");
when(routesNotModifiedFile1.lastModified()).thenReturn(now);
routes.add(routesNotModifiedFile1);

Play.routes = routes;

HashMap<String, VirtualFile> modulesRoutes = new HashMap<>();
VirtualFile moduleRoute1 = mock(VirtualFile.class);
when(moduleRoute1.lastModified()).thenReturn(now-1000);
VirtualFile moduleRoute2 = mock(VirtualFile.class);
when(moduleRoute2.lastModified()).thenReturn(now);
modulesRoutes.put("1",moduleRoute1);
modulesRoutes.put("2",moduleRoute2);

Play.modulesRoutes=modulesRoutes;

Router.detectChanges("");
}

@Test
public void test_reverseMultiLangRoutes(){
Play.configuration = new Properties();
List<String> applicationLangs = new ArrayList<>();
applicationLangs.add("ru");
applicationLangs.add("fr_FR");
applicationLangs.add("en_GB");
Play.langs=applicationLangs;
Play.multilangRouteFiles=true;

Router.appendRoute("GET","/test/action","testAction","","","conf/routes.en_GB",0);
Router.appendRoute("GET","/test/deistvie","testAction","","","conf/routes.ru",1);
Router.appendRoute("GET","/test/activite","testAction","","","conf/routes.fr_FR",2);
Router.appendRoute("GET","/test/act","testAnotherAction","","","conf/routes.fr_FR",3);
Router.appendRoute("GET","/test/akt","testAnotherAction","","","conf/routes.ru",4);
Router.appendRoute("GET","/test/active","testAnotherAction","","","conf/routes.en_GB",5);

Lang.change("ru");
Router.ActionDefinition testAction = Router.reverse("testAction", new HashMap<String, Object>());
assertEquals("/test/deistvie",testAction.url);

Lang.change("en_GB");
testAction = Router.reverse("testAction", new HashMap<String, Object>());
assertEquals("/test/action",testAction.url);

Lang.change("fr_FR");
testAction = Router.reverse("testAction", new HashMap<String, Object>());
assertEquals("/test/activite",testAction.url);

Router.routes.clear();
Lang.change("en_GB");
Router.appendRoute("GET","/test/do","doAction","","","conf/routes.en_GB",0);
Router.appendRoute("GET","/test/delo","doAction","","","conf/routes.ru",1);
testAction = Router.reverse("doAction", new HashMap<String, Object>());
assertEquals("/test/do",testAction.url);
}

@Test
public void test_routeMultilangActivatesLang(){
Play.configuration = new Properties();
List<String> applicationLangs = new ArrayList<>();
applicationLangs.add("ru");
applicationLangs.add("fr_FR");
applicationLangs.add("en_GB");
Play.langs=applicationLangs;
Play.multilangRouteFiles=true;

Router.appendRoute("GET","/test/action","testAction","","","conf/routes.en_GB",0);
Router.appendRoute("GET","/test/deistvie","testAction","","","conf/routes.ru",1);
Router.appendRoute("GET","/test/activite","testAction","","","conf/routes.fr_FR",2);
Router.appendRoute("GET","/test/act","testAnotherAction","","","conf/routes.fr_FR",3);
Router.appendRoute("GET","/test/akt","testAnotherAction","","","conf/routes.ru",4);
Router.appendRoute("GET","/test/active","testAnotherAction","","","conf/routes.en_GB",5);

Lang.change("en_GB");
assertEquals("en_GB",Lang.get());
Http.Request request = mock(Http.Request.class);
request.method="GET";
request.path="/test/activite";
request.format="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
request.domain="github.com";
Router.route(request);
assertEquals("fr_FR",Lang.get());
}
}