From 53d85471645b2a2959919fdc01e39d1eda5fd900 Mon Sep 17 00:00:00 2001 From: Vadim Vohmjanin Date: Tue, 18 Oct 2016 15:42:09 +0300 Subject: [PATCH 1/5] [#2064] Multilangual routes files support --- framework/pym/play/application.py | 3 +- framework/src/play/Play.java | 23 +++++++++++- framework/src/play/mvc/Router.java | 60 +++++++++++++++++++++++++----- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/framework/pym/play/application.py b/framework/pym/play/application.py index 9d5a4cb94a..6d5385e49d 100644 --- a/framework/pym/play/application.py +++ b/framework/pym/play/application.py @@ -4,6 +4,7 @@ import re import shutil import socket +import glob from play.utils import * @@ -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) assert os.path.exists(os.path.join(self.path, 'conf', 'application.conf')) except AssertionError: print "~ Oops. conf/routes or conf/application.conf missing." diff --git a/framework/src/play/Play.java b/framework/src/play/Play.java index 8ee0b4d886..07f4b3622e 100644 --- a/framework/src/play/Play.java +++ b/framework/src/play/Play.java @@ -114,7 +114,7 @@ public boolean isProd() { /** * Main routes file */ - public static VirtualFile routes; + public static List routes; /** * Plugin routes files */ @@ -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 * @@ -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); @@ -321,6 +326,20 @@ public static void init(File root, String id) { Play.initialized = true; } + private static List loadRoutesFiles(VirtualFile appRoot) { + List routes = new ArrayList(); + 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}")){ + routes.add(vf); + Play.multilangRouteFiles = true; + } + } + return routes; + } + public static void guessFrameworkPath() { // Guess the framework path try { diff --git a/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index a6d4c3150f..706c1be937 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -8,6 +8,7 @@ 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; @@ -19,6 +20,7 @@ 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; @@ -50,7 +52,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(); @@ -139,6 +143,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 + "]"); @@ -228,14 +233,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; } } } @@ -293,6 +300,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; } } @@ -591,6 +601,9 @@ private static List getActionRoutes(String action) { matchingRoutes = findActionRoutes(action); actionRoutesCache.put(action, matchingRoutes); } + if(Play.multilangRouteFiles){ + prioritizeActionRoutesBasedOnActiveLocale(matchingRoutes); + } return matchingRoutes; } @@ -617,6 +630,24 @@ private static List findActionRoutes(String action) { return matchingRoutes; } + private static void prioritizeActionRoutesBasedOnActiveLocale(List matchingRoutes) { + if(matchingRoutes.size()==0) return; + String locale = Lang.get(); + if(StringUtils.isEmpty(locale)) return; + for (int i = 0; i < matchingRoutes.size(); i++) { + Router.ActionRoute actionRoute = matchingRoutes.get(i); + if(locale.equals(actionRoute.route.locale)){ + prioritizeMultilangActionRoute(matchingRoutes,i); + } + } + } + + private static void prioritizeMultilangActionRoute(List t, int position){ + if(position!=0) { + t.add(0, t.remove(position)); + } + } + private static final class ActionRoute { private Route route; private Map args = new HashMap<>(2); @@ -736,6 +767,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 = ""; @@ -980,5 +1012,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}")){ + this.locale = fileName.split("\\.")[1]; + } + } } } From 8067cc2bbe32f88a93e8f2f8b30a0701c9d06c66 Mon Sep 17 00:00:00 2001 From: Vadim Vohmjanin Date: Thu, 27 Oct 2016 15:23:08 +0300 Subject: [PATCH 2/5] [#2064] Multilangual routes files support --- framework/pym/play/application.py | 2 +- framework/src/play/Play.java | 4 +- framework/src/play/mvc/Router.java | 26 ++-- framework/test-src/play/mvc/RouterTest.java | 136 +++++++++++++++++++- 4 files changed, 151 insertions(+), 17 deletions(-) diff --git a/framework/pym/play/application.py b/framework/pym/play/application.py index 6d5385e49d..04b2ef7e3e 100644 --- a/framework/pym/play/application.py +++ b/framework/pym/play/application.py @@ -47,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')) or len(glob.glob(os.path.join(self.path, 'conf', 'routes.??')))>0) + 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." diff --git a/framework/src/play/Play.java b/framework/src/play/Play.java index 07f4b3622e..29dbde8349 100644 --- a/framework/src/play/Play.java +++ b/framework/src/play/Play.java @@ -326,13 +326,13 @@ public static void init(File root, String id) { Play.initialized = true; } - private static List loadRoutesFiles(VirtualFile appRoot) { + public static List loadRoutesFiles(VirtualFile appRoot) { List routes = new ArrayList(); 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}")){ + } else if(virtualFileName !=null && virtualFileName.matches("routes\\.[A-Za-z]{2}(_[A-Za-z]{2})?")){ routes.add(vf); Play.multilangRouteFiles = true; } diff --git a/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index 706c1be937..1b40538c85 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -630,23 +630,23 @@ private static List findActionRoutes(String action) { return matchingRoutes; } - private static void prioritizeActionRoutesBasedOnActiveLocale(List 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 matchingRoutes) { if(matchingRoutes.size()==0) return; - String locale = Lang.get(); + final String locale = Lang.get(); if(StringUtils.isEmpty(locale)) return; - for (int i = 0; i < matchingRoutes.size(); i++) { - Router.ActionRoute actionRoute = matchingRoutes.get(i); - if(locale.equals(actionRoute.route.locale)){ - prioritizeMultilangActionRoute(matchingRoutes,i); + matchingRoutes.sort(new Comparator() { + @Override + public int compare(ActionRoute ar1, ActionRoute ar2) { + return locale.equals(ar1.route.locale)?-1:Integer.compare(Play.langs.indexOf(ar1.route.locale),Play.langs.indexOf(ar2.route.locale)); } - } + }); } - private static void prioritizeMultilangActionRoute(List t, int position){ - if(position!=0) { - t.add(0, t.remove(position)); - } - } private static final class ActionRoute { private Route route; @@ -1018,7 +1018,7 @@ private void setLocaleBasedOnMultilangualRoutesFile(String absolutePath){ return; } String fileName = Paths.get(absolutePath).getFileName().toString(); - if(StringUtils.isNotEmpty(fileName) && fileName.matches("routes\\.[A-Za-z]{2}")){ + if(StringUtils.isNotEmpty(fileName) && fileName.matches("routes\\.[A-Za-z]{2}(_[A-Za-z]{2})?")){ this.locale = fileName.split("\\.")[1]; } } diff --git a/framework/test-src/play/mvc/RouterTest.java b/framework/test-src/play/mvc/RouterTest.java index 268f3946c6..76d1ba1fd0 100644 --- a/framework/test-src/play/mvc/RouterTest.java +++ b/framework/test-src/play/mvc/RouterTest.java @@ -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 { @@ -136,4 +142,132 @@ public boolean canRenderFile(Request request){ } return false; } + + @Test + public void test_loadRoutesFiles() { + Play.multilangRouteFiles=false; + + VirtualFile appRoot = mock(VirtualFile.class); + List 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 router = new Router(); + router.lastLoading = now; + + List 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 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 applicationLangs = new ArrayList<>(); + applicationLangs.add("ru"); + applicationLangs.add("fr_FR"); + applicationLangs.add("en_GB"); + Play.langs=applicationLangs; + Play.multilangRouteFiles=true; + + Router router = new Router(); + 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()); + assertEquals("/test/deistvie",testAction.url); + + Lang.change("en_GB"); + testAction = router.reverse("testAction", new HashMap()); + assertEquals("/test/action",testAction.url); + + Lang.change("fr_FR"); + testAction = router.reverse("testAction", new HashMap()); + assertEquals("/test/activite",testAction.url); + } + + @Test + public void test_routeMultilangActivatesLang(){ + Play.configuration = new Properties(); + List applicationLangs = new ArrayList<>(); + applicationLangs.add("ru"); + applicationLangs.add("fr_FR"); + applicationLangs.add("en_GB"); + Play.langs=applicationLangs; + Play.multilangRouteFiles=true; + + Router router = new Router(); + 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()); + } } From 899fc568b63442d70e6e84c7f49483f64aded49f Mon Sep 17 00:00:00 2001 From: Vadim Vohmjanin Date: Fri, 28 Oct 2016 12:25:39 +0300 Subject: [PATCH 3/5] [#2064] Multilangual routes files support --- framework/src/play/mvc/Router.java | 4 +- framework/test-src/play/mvc/RouterTest.java | 47 ++++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index 1b40538c85..b6280b348d 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -642,7 +642,9 @@ private static void prioritizeActionRoutesBasedOnActiveLocale(List() { @Override public int compare(ActionRoute ar1, ActionRoute ar2) { - return locale.equals(ar1.route.locale)?-1:Integer.compare(Play.langs.indexOf(ar1.route.locale),Play.langs.indexOf(ar2.route.locale)); + 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)); } }); } diff --git a/framework/test-src/play/mvc/RouterTest.java b/framework/test-src/play/mvc/RouterTest.java index 76d1ba1fd0..648ea07c48 100644 --- a/framework/test-src/play/mvc/RouterTest.java +++ b/framework/test-src/play/mvc/RouterTest.java @@ -182,8 +182,8 @@ public void test_loadRoutesFiles() { @Test public void test_detectNoChanges() { long now = System.currentTimeMillis(); - Router router = new Router(); - router.lastLoading = now; + + Router.lastLoading = now; List routes = new ArrayList<>(); VirtualFile routesNotModifiedFile = mock(VirtualFile.class); @@ -208,7 +208,7 @@ public void test_detectNoChanges() { Play.modulesRoutes=modulesRoutes; - router.detectChanges(""); + Router.detectChanges(""); } @Test @@ -221,25 +221,31 @@ public void test_reverseMultiLangRoutes(){ Play.langs=applicationLangs; Play.multilangRouteFiles=true; - Router router = new Router(); - 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); + 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()); + Router.ActionDefinition testAction = Router.reverse("testAction", new HashMap()); assertEquals("/test/deistvie",testAction.url); Lang.change("en_GB"); - testAction = router.reverse("testAction", new HashMap()); + testAction = Router.reverse("testAction", new HashMap()); assertEquals("/test/action",testAction.url); Lang.change("fr_FR"); - testAction = router.reverse("testAction", new HashMap()); + testAction = Router.reverse("testAction", new HashMap()); 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()); + assertEquals("/test/do",testAction.url); } @Test @@ -252,13 +258,12 @@ public void test_routeMultilangActivatesLang(){ Play.langs=applicationLangs; Play.multilangRouteFiles=true; - Router router = new Router(); - 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); + 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()); @@ -267,7 +272,7 @@ public void test_routeMultilangActivatesLang(){ 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); + Router.route(request); assertEquals("fr_FR",Lang.get()); } } From 13874587a886bcbab7a4dea7caaa3379fc6bcdc8 Mon Sep 17 00:00:00 2001 From: Vadim Vohmjanin Date: Fri, 28 Oct 2016 15:40:02 +0300 Subject: [PATCH 4/5] Merge remote-tracking branch 'upstream/1.4.x' into lighthouse-2064-patch Conflicts: framework/src/play/mvc/Router.java --- framework/src/play/mvc/Router.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index 1fb69130c8..f29b6000ef 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -1,22 +1,9 @@ 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; From c21cdf1d5403b24919bd9715aff0df814dc64fcc Mon Sep 17 00:00:00 2001 From: Vadim Vohmjanin Date: Tue, 31 Jan 2017 15:24:44 +0200 Subject: [PATCH 5/5] [#2064] Multilanguage routes fixed backward compatibility --- framework/src/play/Play.java | 23 +++++----- framework/src/play/mvc/Router.java | 38 ++++++++++------- framework/test-src/play/mvc/RouterTest.java | 47 +++++++++++++++------ 3 files changed, 67 insertions(+), 41 deletions(-) diff --git a/framework/src/play/Play.java b/framework/src/play/Play.java index 29dbde8349..a83efd1dcf 100644 --- a/framework/src/play/Play.java +++ b/framework/src/play/Play.java @@ -114,7 +114,13 @@ public boolean isProd() { /** * Main routes file */ - public static List routes; + public static VirtualFile routes; + + /** + * Main routes file + */ + public static List internationalizedRoutes; + /** * Plugin routes files */ @@ -182,11 +188,6 @@ 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 * @@ -281,7 +282,8 @@ public static void init(File root, String id) { } // Main route file - routes = loadRoutesFiles(appRoot); + routes = appRoot.child("conf/routes"); + internationalizedRoutes = loadMultilanguageRoutesFiles(appRoot); // Plugin route files modulesRoutes = new HashMap<>(16); @@ -326,15 +328,12 @@ public static void init(File root, String id) { Play.initialized = true; } - public static List loadRoutesFiles(VirtualFile appRoot) { + public static List loadMultilanguageRoutesFiles(VirtualFile appRoot) { List routes = new ArrayList(); 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})?")){ + if(virtualFileName !=null && virtualFileName.matches("routes\\.[A-Za-z]{2}(_[A-Za-z]{2})?")){ routes.add(vf); - Play.multilangRouteFiles = true; } } return routes; diff --git a/framework/src/play/mvc/Router.java b/framework/src/play/mvc/Router.java index f29b6000ef..d6e72bd946 100644 --- a/framework/src/play/mvc/Router.java +++ b/framework/src/play/mvc/Router.java @@ -3,6 +3,7 @@ import jregex.Matcher; import jregex.Pattern; import jregex.REFlags; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import play.Logger; import play.Play; @@ -51,7 +52,8 @@ public class Router { public static void load(String prefix) { routes.clear(); actionRoutesCache.clear(); - for (VirtualFile routeFile : Play.routes) { + parse(Play.routes, prefix); + for (VirtualFile routeFile : Play.internationalizedRoutes) { parse(routeFile, prefix); } lastLoading = System.currentTimeMillis(); @@ -227,14 +229,18 @@ public static void detectChanges(String prefix) { if (Play.mode == Mode.PROD && lastLoading > 0) { return; } - for (VirtualFile route : Play.routes) { - if (route.lastModified() > lastLoading) { - load(prefix); - return; + if (Play.routes.lastModified() > lastLoading) { + load(prefix); + } else { + for (VirtualFile file : Play.modulesRoutes.values()) { + if (file.lastModified() > lastLoading) { + load(prefix); + return; + } } } - for (VirtualFile file : Play.modulesRoutes.values()) { - if (file.lastModified() > lastLoading) { + for (VirtualFile route : Play.internationalizedRoutes) { + if (route.lastModified() > lastLoading) { load(prefix); return; } @@ -294,7 +300,7 @@ 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())){ + if(CollectionUtils.isNotEmpty(Play.internationalizedRoutes) && StringUtils.isNotEmpty(route.locale) && !route.locale.equals(Lang.get())){ Lang.change(route.locale); } return route; @@ -593,8 +599,8 @@ private static List getActionRoutes(String action) { matchingRoutes = findActionRoutes(action); actionRoutesCache.put(action, matchingRoutes); } - if(Play.multilangRouteFiles){ - prioritizeActionRoutesBasedOnActiveLocale(matchingRoutes); + if(CollectionUtils.isNotEmpty(Play.internationalizedRoutes)){ + matchingRoutes = prioritizeActionRoutesBasedOnActiveLocale(matchingRoutes); } return matchingRoutes; } @@ -627,11 +633,12 @@ private static List findActionRoutes(String action) { * * @param matchingRoutes */ - private static void prioritizeActionRoutesBasedOnActiveLocale(List matchingRoutes) { - if(matchingRoutes.size()==0) return; - final String locale = Lang.get(); - if(StringUtils.isEmpty(locale)) return; - matchingRoutes.sort(new Comparator() { + private static List prioritizeActionRoutesBasedOnActiveLocale(List matchingRoutes) { + if(matchingRoutes.size()==0) return matchingRoutes; + final String locale = Lang.get(); + if(StringUtils.isEmpty(locale)) return matchingRoutes; + List sortedMatchingRoutes = new ArrayList<>(matchingRoutes); + sortedMatchingRoutes.sort(new Comparator() { @Override public int compare(ActionRoute ar1, ActionRoute ar2) { if(locale.equals(ar1.route.locale)) return -1; @@ -639,6 +646,7 @@ public int compare(ActionRoute ar1, ActionRoute ar2) { return Integer.compare(Play.langs.indexOf(ar1.route.locale), Play.langs.indexOf(ar2.route.locale)); } }); + return sortedMatchingRoutes; } diff --git a/framework/test-src/play/mvc/RouterTest.java b/framework/test-src/play/mvc/RouterTest.java index 648ea07c48..91b454f111 100644 --- a/framework/test-src/play/mvc/RouterTest.java +++ b/framework/test-src/play/mvc/RouterTest.java @@ -1,6 +1,6 @@ package play.mvc; -import org.junit.Test; +import org.junit.*; import play.Play; import play.i18n.Lang; import play.mvc.Http.Request; @@ -20,6 +20,13 @@ public class RouterTest { + @org.junit.Before public void initialize() { + Router.routes.clear(); + Play.internationalizedRoutes = null; + Play.routes = null; + Play.configuration = null; + } + @Test public void test_getBaseUrl() { @@ -145,7 +152,9 @@ public boolean canRenderFile(Request request){ @Test public void test_loadRoutesFiles() { - Play.multilangRouteFiles=false; + + Play.internationalizedRoutes = mock(ArrayList.class); + when(Play.internationalizedRoutes.size()).thenReturn(3); VirtualFile appRoot = mock(VirtualFile.class); List routes = new ArrayList<>(); @@ -155,14 +164,16 @@ public void test_loadRoutesFiles() { VirtualFile appConf = mock(VirtualFile.class); when(appConf.getName()).thenReturn("application.conf"); routes.add(appConf); + VirtualFile multilangRoutes = mock(VirtualFile.class); + when(multilangRoutes.getName()).thenReturn("routes.en_GB"); + routes.add(multilangRoutes); 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); + assertEquals(1,Play.loadMultilanguageRoutesFiles(appRoot).size()); routes = new ArrayList<>(); VirtualFile routesEnFile = mock(VirtualFile.class); @@ -174,8 +185,8 @@ public void test_loadRoutesFiles() { routes.add(appConf); when(confFolder.list()).thenReturn(routes); - assertEquals(2,Play.loadRoutesFiles(appRoot).size()); - assertEquals(true,Play.multilangRouteFiles); + assertEquals(2,Play.loadMultilanguageRoutesFiles(appRoot).size()); + assertEquals(true,Play.internationalizedRoutes.size()>0); } @@ -185,18 +196,23 @@ public void test_detectNoChanges() { Router.lastLoading = now; - List routes = new ArrayList<>(); VirtualFile routesNotModifiedFile = mock(VirtualFile.class); - when(routesNotModifiedFile.getName()).thenReturn("routes.en"); - when(routesNotModifiedFile.lastModified()).thenReturn(now-1000); - routes.add(routesNotModifiedFile); + when(routesNotModifiedFile.getName()).thenReturn("routes"); + when(routesNotModifiedFile.lastModified()).thenReturn(now-2000); + Play.routes = routesNotModifiedFile; + + List internationalizedRoutes = new ArrayList<>(); + VirtualFile routesNotModifiedFile2 = mock(VirtualFile.class); + when(routesNotModifiedFile2.getName()).thenReturn("routes.en"); + when(routesNotModifiedFile2.lastModified()).thenReturn(now-1000); + internationalizedRoutes.add(routesNotModifiedFile2); VirtualFile routesNotModifiedFile1 = mock(VirtualFile.class); when(routesNotModifiedFile1.getName()).thenReturn("routes.ru_RU"); when(routesNotModifiedFile1.lastModified()).thenReturn(now); - routes.add(routesNotModifiedFile1); + internationalizedRoutes.add(routesNotModifiedFile1); - Play.routes = routes; + Play.internationalizedRoutes = internationalizedRoutes; HashMap modulesRoutes = new HashMap<>(); VirtualFile moduleRoute1 = mock(VirtualFile.class); @@ -219,7 +235,9 @@ public void test_reverseMultiLangRoutes(){ applicationLangs.add("fr_FR"); applicationLangs.add("en_GB"); Play.langs=applicationLangs; - Play.multilangRouteFiles=true; + + Play.internationalizedRoutes = mock(ArrayList.class); + when(Play.internationalizedRoutes.size()).thenReturn(3); Router.appendRoute("GET","/test/action","testAction","","","conf/routes.en_GB",0); Router.appendRoute("GET","/test/deistvie","testAction","","","conf/routes.ru",1); @@ -256,7 +274,8 @@ public void test_routeMultilangActivatesLang(){ applicationLangs.add("fr_FR"); applicationLangs.add("en_GB"); Play.langs=applicationLangs; - Play.multilangRouteFiles=true; + Play.internationalizedRoutes = mock(ArrayList.class); + when(Play.internationalizedRoutes.size()).thenReturn(3); Router.appendRoute("GET","/test/action","testAction","","","conf/routes.en_GB",0); Router.appendRoute("GET","/test/deistvie","testAction","","","conf/routes.ru",1);