diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..006bc2f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+root = true
+
+[*]
+end_of_line = lf
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2a5f403
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,14 @@
+# General line-ending rules
+*.as text eol=lf
+*.bash text eol=lf
+*.conf text eol=lf
+*.ftl text eol=lf
+*.h text eol=lf
+*.java text eol=lf
+*.jnlp text eol=lf
+*.js text eol=lf
+*.less text eol=lf
+*.properties text eol=lf
+*.py text eol=lf
+*.sh text eol=lf
+*.sql text eol=lf
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e0cb397..4bd21ff 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- java-version: [8, 11, 17]
+ java-version: [17, 21, 25]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
diff --git a/.gitignore b/.gitignore
index 5755aba..4445f8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,23 @@
-.classpath
-.project
-.settings/
-*.iml
-.idea/
-
-/code/target
-/code/*.iml
-/code/*.ipr
-/code/*.iws
-/code/.idea
-/dreamcatcher/target
-/dreamcatcher/*.iml
-/dreamcatcher/*.ipr
-/dreamcatcher/*.iws
-/dreamcatcher/.idea
-/code/clean.bat
-.idea
-target
-/samples/helloworld/helloworld.iml
-/samples/helloworld/helloworld.log
-/samples/bloggy/*.iml
-/samples/bloggy/*.log
+.classpath
+.project
+.settings/
+*.iml
+.idea/
+
+/code/target
+/code/*.iml
+/code/*.ipr
+/code/*.iws
+/code/.idea
+/dreamcatcher/target
+/dreamcatcher/*.iml
+/dreamcatcher/*.ipr
+/dreamcatcher/*.iws
+/dreamcatcher/.idea
+/code/clean.bat
+.idea
+target
+/samples/helloworld/helloworld.iml
+/samples/helloworld/helloworld.log
+/samples/bloggy/*.iml
+/samples/bloggy/*.log
diff --git a/code/bundle.bat b/code/bundle.bat
index 0a12ae4..9d58cf3 100644
--- a/code/bundle.bat
+++ b/code/bundle.bat
@@ -1,9 +1,9 @@
-setlocal enabledelayedexpansion
-
-if not "!JAVA8_64_HOME!"=="" (
- set PATH=!JAVA8_64_HOME!\bin;!PATH!
- set JAVA_HOME=!JAVA8_64_HOME!
-)
-
-call mvn.cmd validate --batch-mode
-call mvn.cmd -Dfile.encoding=UTF-8 -DcreateChecksum=true clean source:jar javadoc:jar repository:bundle-create install --batch-mode
+setlocal enabledelayedexpansion
+
+if not "!JAVA17_64_HOME!"=="" (
+ set PATH=!JAVA17_64_HOME!\bin;!PATH!
+ set JAVA_HOME=!JAVA17_64_HOME!
+)
+
+call mvn.cmd validate --batch-mode
+call mvn.cmd -Dfile.encoding=UTF-8 -DcreateChecksum=true clean source:jar javadoc:jar repository:bundle-create install --batch-mode
diff --git a/code/lib/install.bat b/code/lib/install.bat
index 4337961..999e161 100644
--- a/code/lib/install.bat
+++ b/code/lib/install.bat
@@ -1 +1 @@
-mvn install:install-file -Dfile="guice-5.0.2-SNAPSHOT.jar" -DgroupId=com.google.inject -DartifactId=guice -Dversion=5.0.2-SNAPSHOT -Dpackaging=jar
+mvn install:install-file -Dfile="guice-5.0.2-SNAPSHOT.jar" -DgroupId=com.google.inject -DartifactId=guice -Dversion=5.0.2-SNAPSHOT -Dpackaging=jar
diff --git a/code/pom.xml b/code/pom.xml
index 26283a9..2719c09 100644
--- a/code/pom.xml
+++ b/code/pom.xml
@@ -1,370 +1,380 @@
-
-
- 4.0.0
- org.nocturne
- nocturne
- 1.4.0-SNAPSHOT
- jar
-
- https://github.com/Codeforces/nocturne/
- 2009-2021
- Nocturne web framework
-
- Nocturne is a lightweight Java MVC framework with hot-swap any code feature out of the box
-
-
-
-
- MikeMirzayanov
- Mike Mirzayanov
- mirzayanovmr@gmail.com
-
- owner, author
-
- +3
-
-
-
-
-
-
- The Apache Software License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
- repo
-
-
-
-
- github
- GitHub Packages
- https://maven.pkg.github.com/Codeforces/nocturne
-
-
-
- scm:git:git://github.com/Codeforces/nocturne.git
- scm:git:git://github.com/Codeforces/nocturne.git
- https://github.com/Codeforces/nocturne/
-
-
- GitHub Issues
- https://github.com/Codeforces/nocturne/issues
-
-
-
-
- org.freemarker
- freemarker
- 2.3.31
-
-
- cglib
- cglib
- 3.3.0
-
-
- com.google.code.findbugs
- jsr305
- 3.0.2
-
-
- org.ow2.asm
- asm
- 9.2
-
-
- org.ow2.asm
- asm-tree
- 9.2
-
-
- org.ow2.asm
- asm-analysis
- 9.2
-
-
- org.ow2.asm
- asm-util
- 9.2
-
-
- org.apache.commons
- commons-lang3
- 3.12.0
-
-
- commons-io
- commons-io
- 2.11.0
-
-
- commons-fileupload
- commons-fileupload
- 1.4
-
-
- com.google.code.gson
- gson
- 2.8.8
-
-
- com.google.guava
- guava
- 31.0.1-jre
-
-
-
- com.google.inject
- guice
- 5.0.2-SNAPSHOT
-
-
- javax.inject
- javax.inject
- 1
-
-
- aopalliance
- aopalliance
- 1.0
-
-
- eu.medsea.mimeutil
- mime-util
- 2.1.3
-
-
- org.slf4j
- slf4j-api
-
-
- org.slf4j
- slf4j-log4j12
-
-
-
-
- log4j
- log4j
- 1.2.17
-
-
- org.slf4j
- slf4j-api
- 1.7.32
-
-
- org.slf4j
- slf4j-log4j12
- 1.7.32
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.13.0
-
-
- com.fasterxml.jackson.core
- jackson-annotations
- 2.13.0
-
-
- com.fasterxml.jackson.core
- jackson-core
- 2.13.0
-
-
- com.maxmind.geoip2
- geoip2
- 2.15.0
-
-
- com.fasterxml.jackson.core
- jackson-core
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
- org.apache.httpcomponents
- httpclient
-
-
-
-
- com.github.sommeri
- less4j
- 1.17.2
-
-
- commons-beanutils
- commons-beanutils
-
-
- com.google.protobuf
- protobuf-java
-
-
-
-
- commons-beanutils
- commons-beanutils
- 1.9.4
-
-
- javax.servlet
- javax.servlet-api
- 4.0.1
- provided
-
-
- org.jetbrains
- annotations
- 24.0.1
-
-
- junit
- junit
- 4.13.2
- test
-
-
- io.prometheus
- simpleclient
- 0.16.0
-
-
-
-
- nocturne
-
-
- org.apache.maven.plugins
- maven-install-plugin
- 2.5.2
-
-
- install-guice
-
- install-file
-
- validate
-
- com.google.inject
- guice
- 5.0.2-SNAPSHOT
- jar
- ${pom.basedir}/lib/guice-5.0.2-SNAPSHOT.jar
- true
-
-
-
-
-
- org.apache.maven.plugins
- maven-repository-plugin
- 2.4
-
-
- org.codehaus.plexus
- plexus-archiver
- 4.2.5
-
-
-
-
- org.apache.maven
- maven-plugin-api
- 3.8.3
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
- 1.8
- 1.8
- UTF-8
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- 3.2.0
-
- UTF-8
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.22.2
-
- -Dfile.encoding=UTF-8
- -Xmx1200M
-
-
-
-
-
- org.basepom.maven
- duplicate-finder-maven-plugin
- 1.5.0
-
-
- default
- verify
-
- check
-
-
-
-
- false
- false
- true
- true
- true
- false
- false
- false
- false
- true
-
-
- false
- sun.boot.class.path
-
-
-
-
- true
-
-
-
-
-
-
- src/main/resources
- true
-
-
- src/main/files
- false
-
-
-
-
-
- UTF-8
-
-
+
+
+ 4.0.0
+ org.nocturne
+ nocturne
+ 1.4.0-SNAPSHOT
+ jar
+
+ https://github.com/Codeforces/nocturne/
+ 2009-2021
+ Nocturne web framework
+
+ Nocturne is a lightweight Java MVC framework with hot-swap any code feature out of the box
+
+
+
+
+ MikeMirzayanov
+ Mike Mirzayanov
+ mirzayanovmr@gmail.com
+
+ owner, author
+
+ +3
+
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ github
+ GitHub Packages
+ https://maven.pkg.github.com/Codeforces/nocturne
+
+
+
+ scm:git:git://github.com/Codeforces/nocturne.git
+ scm:git:git://github.com/Codeforces/nocturne.git
+ https://github.com/Codeforces/nocturne/
+
+
+ GitHub Issues
+ https://github.com/Codeforces/nocturne/issues
+
+
+
+
+ org.freemarker
+ freemarker
+ 2.3.31
+
+
+ cglib
+ cglib
+ 3.3.0
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+
+
+ org.ow2.asm
+ asm
+ 9.2
+
+
+ org.ow2.asm
+ asm-tree
+ 9.2
+
+
+ org.ow2.asm
+ asm-analysis
+ 9.2
+
+
+ org.ow2.asm
+ asm-util
+ 9.2
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ commons-codec
+ commons-codec
+ 1.20.0
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+ commons-fileupload
+ commons-fileupload
+ 1.4
+
+
+ com.google.code.gson
+ gson
+ 2.8.8
+
+
+ com.google.guava
+ guava
+ 31.0.1-jre
+
+
+
+ com.google.inject
+ guice
+ 5.0.2-SNAPSHOT
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+ aopalliance
+ aopalliance
+ 1.0
+
+
+ eu.medsea.mimeutil
+ mime-util
+ 2.1.3
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+
+ log4j
+ log4j
+ 1.2.17
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.32
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.32
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.20.1
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.20
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.20.1
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-cbor
+ 2.20.1
+
+
+ com.maxmind.geoip2
+ geoip2
+ 4.4.0
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+
+ com.github.sommeri
+ less4j
+ 1.17.2
+
+
+ commons-beanutils
+ commons-beanutils
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+
+
+ commons-beanutils
+ commons-beanutils
+ 1.9.4
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+ org.jetbrains
+ annotations
+ 24.0.1
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ io.prometheus
+ simpleclient
+ 0.16.0
+
+
+
+
+ nocturne
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 2.5.2
+
+
+ install-guice
+
+ install-file
+
+ validate
+
+ com.google.inject
+ guice
+ 5.0.2-SNAPSHOT
+ jar
+ ${pom.basedir}/lib/guice-5.0.2-SNAPSHOT.jar
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-repository-plugin
+ 2.4
+
+
+ org.codehaus.plexus
+ plexus-archiver
+ 4.2.5
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ 3.8.3
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.1
+
+ 17
+ 17
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.2.0
+
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+ -Dfile.encoding=UTF-8
+ -Xmx1200M
+
+
+
+
+
+ org.basepom.maven
+ duplicate-finder-maven-plugin
+ 1.5.0
+
+
+ default
+ verify
+
+ check
+
+
+
+
+ false
+ false
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ true
+
+
+ false
+ sun.boot.class.path
+
+
+
+
+ true
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+ src/main/files
+ false
+
+
+
+
+
+ UTF-8
+
+
diff --git a/code/src/main/java/org/nocturne/annotation/Action.java b/code/src/main/java/org/nocturne/annotation/Action.java
index efbee0b..4d830f7 100644
--- a/code/src/main/java/org/nocturne/annotation/Action.java
+++ b/code/src/main/java/org/nocturne/annotation/Action.java
@@ -1,41 +1,41 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.annotation;
-
-import org.nocturne.main.HttpMethod;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * Set it before page method which should be executed on specified
- * action. Action is a string parameter with name "action".
- *
- *
- * If you have set @Action without value, it means that default action
- * became the annotated method but not action().
- *
- *
- * If there is validation method (see @Validate.class) action method
- * will be invoked only on if validation passed.
- *
- *
- * @author Mike Mirzayanov
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface Action {
- /**
- * @return Action name.
- */
- String value() default "";
-
- /**
- * @return HTTP method to be handled by the action.
- */
- HttpMethod[] method() default {HttpMethod.GET};
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.annotation;
+
+import org.nocturne.main.HttpMethod;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * Set it before page method which should be executed on specified
+ * action. Action is a string parameter with name "action".
+ *
+ *
+ * If you have set @Action without value, it means that default action
+ * became the annotated method but not action().
+ *
+ *
+ * If there is validation method (see @Validate.class) action method
+ * will be invoked only on if validation passed.
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Action {
+ /**
+ * @return Action name.
+ */
+ String value() default "";
+
+ /**
+ * @return HTTP method to be handled by the action.
+ */
+ HttpMethod[] method() default {HttpMethod.GET};
+}
diff --git a/code/src/main/java/org/nocturne/annotation/Invalid.java b/code/src/main/java/org/nocturne/annotation/Invalid.java
index 7421c0f..0bbe05f 100644
--- a/code/src/main/java/org/nocturne/annotation/Invalid.java
+++ b/code/src/main/java/org/nocturne/annotation/Invalid.java
@@ -1,25 +1,25 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Set it before page method which should be executed for specified
- * action if validation fails. Action method will no be executed
- * in this case.
- *
- * @author Mike Mirzayanov
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface Invalid {
- /**
- * @return Action name.
- */
- String value() default "";
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Set it before page method which should be executed for specified
+ * action if validation fails. Action method will no be executed
+ * in this case.
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Invalid {
+ /**
+ * @return Action name.
+ */
+ String value() default "";
+}
diff --git a/code/src/main/java/org/nocturne/annotation/Name.java b/code/src/main/java/org/nocturne/annotation/Name.java
index caf14bc..2090cd4 100644
--- a/code/src/main/java/org/nocturne/annotation/Name.java
+++ b/code/src/main/java/org/nocturne/annotation/Name.java
@@ -1,30 +1,30 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * You can specify page name and later easily get links
- * using it.
- *
- *
- * If you don't specify name for page, the
- * page name is equals to page.getClass().getSimpleName().
- *
- *
- * @author Mike Mirzayanov
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Name {
- /**
- * @return Page name (you can use this value as page name to find its link).
- */
- String value();
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * You can specify page name and later easily get links
+ * using it.
+ *
+ *
+ * If you don't specify name for page, the
+ * page name is equals to page.getClass().getSimpleName().
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Name {
+ /**
+ * @return Page name (you can use this value as page name to find its link).
+ */
+ String value();
+}
diff --git a/code/src/main/java/org/nocturne/annotation/Parameter.java b/code/src/main/java/org/nocturne/annotation/Parameter.java
index 7d0fbc2..6c28967 100644
--- a/code/src/main/java/org/nocturne/annotation/Parameter.java
+++ b/code/src/main/java/org/nocturne/annotation/Parameter.java
@@ -1,114 +1,114 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-/**
- *
- * If your component (page or frame) has fields marked with
- * a @Parameter then nocturne will set them automatically before
- * beforeAction phase. Also nocturne takes care about parameters of
- * action/validate/invalid Controller method parameters.
- *
- *
- * Strip mode controls valid values for parameter.
- *
- *
- * Name means name of the parameter
- * as GET or POST parameter or part
- * of overrideParameters in the response of RequestRouter (LinkedRequestRouter places
- * {param} shortcuts in it).
- *
- *
- * @author Mike Mirzayanov
- */
-@Retention(RUNTIME)
-@Target({FIELD, PARAMETER})
-public @interface Parameter {
- /**
- * @return POST or GET parameter name. Don't set it if it is the same with class field name.
- */
- String name() default "";
-
- /**
- * @return What strategy to strip characters to use. Default value is the most strict StripMode.ID.
- */
- StripMode stripMode() default StripMode.ID;
-
- /**
- * How to strip parameter value.
- */
- enum StripMode {
- /**
- * Do not strip any characters.
- */
- NONE {
- @Override
- public String strip(String value) {
- return value;
- }
- },
-
- /**
- * Leave only safe chars: strip slashes, quotes, angle brackets, ampersand and low-code chars. Also makes trim().
- */
- SAFE {
- @Override
- public String strip(String value) {
- if (value != null) {
- char[] chars = value.toCharArray();
- StringBuilder sb = new StringBuilder(chars.length);
- for (char c : chars) {
- if (c != '/' && c != '&' && c != '<' && c != '>' && c != '\\' && c != '\"' && c != '\'' && c >= ' ') {
- sb.append(c);
- }
- }
- return sb.toString().trim();
- } else {
- return value;
- }
- }
- },
-
- /**
- * Leave only chars which can be part of java ID (see Character.isJavaIdentifierPart).
- */
- ID {
- private boolean isValidChar(char c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
- || (c >= '0' && c <= '9') || (c == '_')
- || (c == '-')
- || (c == '.');
- }
-
- @Override
- public String strip(String value) {
- if (value != null) {
- char[] chars = value.toCharArray();
- StringBuilder sb = new StringBuilder(chars.length);
- for (char c : chars) {
- if (isValidChar(c)) {
- sb.append(c);
- }
- }
- return sb.toString();
- } else {
- return value;
- }
- }
- };
-
- /**
- * @param value Value to be stripped.
- * @return Processed value.
- */
- public abstract String strip(String value);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ *
+ * If your component (page or frame) has fields marked with
+ * a @Parameter then nocturne will set them automatically before
+ * beforeAction phase. Also nocturne takes care about parameters of
+ * action/validate/invalid Controller method parameters.
+ *
+ *
+ * Strip mode controls valid values for parameter.
+ *
+ *
+ * Name means name of the parameter
+ * as GET or POST parameter or part
+ * of overrideParameters in the response of RequestRouter (LinkedRequestRouter places
+ * {param} shortcuts in it).
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER})
+public @interface Parameter {
+ /**
+ * @return POST or GET parameter name. Don't set it if it is the same with class field name.
+ */
+ String name() default "";
+
+ /**
+ * @return What strategy to strip characters to use. Default value is the most strict StripMode.ID.
+ */
+ StripMode stripMode() default StripMode.ID;
+
+ /**
+ * How to strip parameter value.
+ */
+ enum StripMode {
+ /**
+ * Do not strip any characters.
+ */
+ NONE {
+ @Override
+ public String strip(String value) {
+ return value;
+ }
+ },
+
+ /**
+ * Leave only safe chars: strip slashes, quotes, angle brackets, ampersand and low-code chars. Also makes trim().
+ */
+ SAFE {
+ @Override
+ public String strip(String value) {
+ if (value != null) {
+ char[] chars = value.toCharArray();
+ StringBuilder sb = new StringBuilder(chars.length);
+ for (char c : chars) {
+ if (c != '/' && c != '&' && c != '<' && c != '>' && c != '\\' && c != '\"' && c != '\'' && c >= ' ') {
+ sb.append(c);
+ }
+ }
+ return sb.toString().trim();
+ } else {
+ return value;
+ }
+ }
+ },
+
+ /**
+ * Leave only chars which can be part of java ID (see Character.isJavaIdentifierPart).
+ */
+ ID {
+ private boolean isValidChar(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || (c == '_')
+ || (c == '-')
+ || (c == '.');
+ }
+
+ @Override
+ public String strip(String value) {
+ if (value != null) {
+ char[] chars = value.toCharArray();
+ StringBuilder sb = new StringBuilder(chars.length);
+ for (char c : chars) {
+ if (isValidChar(c)) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ } else {
+ return value;
+ }
+ }
+ };
+
+ /**
+ * @param value Value to be stripped.
+ * @return Processed value.
+ */
+ public abstract String strip(String value);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/annotation/Validate.java b/code/src/main/java/org/nocturne/annotation/Validate.java
index 9659965..df0ec42 100644
--- a/code/src/main/java/org/nocturne/annotation/Validate.java
+++ b/code/src/main/java/org/nocturne/annotation/Validate.java
@@ -1,28 +1,28 @@
-package org.nocturne.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * Set it before page method which should be executed on specified
- * action as validation method. The method should return boolean and
- * the value will define future workflow.
- *
- *
- * Typically validation methods has the line "return runValidation();" as
- * the last line.
- *
- *
- * @author Mike Mirzayanov
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface Validate {
- /**
- * @return Action name.
- */
- String value() default "";
-}
+package org.nocturne.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * Set it before page method which should be executed on specified
+ * action as validation method. The method should return boolean and
+ * the value will define future workflow.
+ *
+ *
+ * Typically validation methods has the line "return runValidation();" as
+ * the last line.
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Validate {
+ /**
+ * @return Action name.
+ */
+ String value() default "";
+}
diff --git a/code/src/main/java/org/nocturne/cache/CacheHandler.java b/code/src/main/java/org/nocturne/cache/CacheHandler.java
index 5d3728f..f38b283 100644
--- a/code/src/main/java/org/nocturne/cache/CacheHandler.java
+++ b/code/src/main/java/org/nocturne/cache/CacheHandler.java
@@ -1,27 +1,27 @@
-package org.nocturne.cache;
-
-import org.nocturne.main.Component;
-
-/**
- *
- * Use implementation of this interface in your pages or frames
- * if you want them to be cached.
- *
- *
- * Method #intercept will be called after Component.prepareForAction
- * and if it returns non-null string it will be used as parsed result instead
- * of typical life-cycle, i.e. methods initializeAction(), Events.fireBeforeAction(this),
- * ..., Events.fireAfterAction(this), finalizeAction()) will not be called.
- *
- *
- * But if it returns null, the typical life-cycle will be used and parsed component
- * will be passed as a result to #postprocess().
- *
- *
- * @author Mike Mirzayanov (mirzayanovmr@gmail.com)
- */
-public interface CacheHandler {
- String intercept(Component component);
-
- void postprocess(Component component, String result);
-}
+package org.nocturne.cache;
+
+import org.nocturne.main.Component;
+
+/**
+ *
+ * Use implementation of this interface in your pages or frames
+ * if you want them to be cached.
+ *
+ *
+ * Method #intercept will be called after Component.prepareForAction
+ * and if it returns non-null string it will be used as parsed result instead
+ * of typical life-cycle, i.e. methods initializeAction(), Events.fireBeforeAction(this),
+ * ..., Events.fireAfterAction(this), finalizeAction()) will not be called.
+ *
+ *
+ * But if it returns null, the typical life-cycle will be used and parsed component
+ * will be passed as a result to #postprocess().
+ *
+ *
+ * @author Mike Mirzayanov (mirzayanovmr@gmail.com)
+ */
+public interface CacheHandler {
+ String intercept(Component component);
+
+ void postprocess(Component component, String result);
+}
diff --git a/code/src/main/java/org/nocturne/caption/CaptionDirective.java b/code/src/main/java/org/nocturne/caption/CaptionDirective.java
index 4580dc4..031e290 100644
--- a/code/src/main/java/org/nocturne/caption/CaptionDirective.java
+++ b/code/src/main/java/org/nocturne/caption/CaptionDirective.java
@@ -1,109 +1,109 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.caption;
-
-import freemarker.core.Environment;
-import freemarker.template.*;
-import org.nocturne.main.ApplicationContext;
-
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-/**
- *
- * Generates caption value by shortcut and args. It injected to any page or frame.
- *
- * Examples:
- *
- * {@literal <@caption params=["Mike"]>Hello, {0}@caption>}
- * {@literal <@caption>Login@caption>}
- * {@literal <@caption key="Login"/>}
- * {@literal <@caption key="Hello, {0}" params=["Mike"]/>}
- *
- *
- * @author Mike Mirzayanov
- */
-@SuppressWarnings("Singleton")
-public class CaptionDirective implements TemplateDirectiveModel {
- /**
- * Singleton instance.
- */
- private static final CaptionDirective INSTANCE = new CaptionDirective();
-
- private static final Object[] EMPTY_OBJECT_ARRAY = {};
-
- private CaptionDirective() {
- // No operations.
- }
-
- @Override
- @SuppressWarnings({"unchecked"})
- public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
- throws TemplateException, IOException {
- char[] chars = null;
-
- if (body != null) {
- CharArrayWriter writer = new CharArrayWriter();
- body.render(writer);
- writer.close();
- chars = writer.toCharArray();
- }
-
- if (params.containsKey("key") && chars != null && chars.length > 0) {
- throw new TemplateModelException(
- "Caption directive expects key parameter or directive body, but not in the same time."
- );
- }
-
- if (!params.containsKey("key") && (chars == null || chars.length == 0)) {
- throw new TemplateModelException(
- "Caption directive expects either key parameter or directive body, but none found."
- );
- }
-
- Set keys = params.keySet();
-
- String key;
- if (params.containsKey("key")) {
- key = params.get("key").toString();
- } else {
- key = String.valueOf(chars);
- }
- keys.remove("key");
-
- Object p = params.get("params");
- keys.remove("params");
-
- if (!keys.isEmpty()) {
- throw new TemplateModelException("Caption directive contains unexpected params.");
- }
-
- Object[] args = EMPTY_OBJECT_ARRAY;
-
- if (p instanceof SimpleSequence) {
- SimpleSequence sequence = (SimpleSequence) p;
- args = new Object[sequence.size()];
- for (int i = 0; i < sequence.size(); i++) {
- Object arg = sequence.get(i);
- if (arg instanceof SimpleNumber) {
- args[i] = ((TemplateNumberModel) arg).getAsNumber();
- } else {
- args[i] = arg.toString();
- }
- }
- }
-
- String value = ApplicationContext.getInstance().$(key, args);
- env.getOut().write(value);
- }
-
- /**
- * @return Returns singleton instance.
- */
- public static CaptionDirective getInstance() {
- return INSTANCE;
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.caption;
+
+import freemarker.core.Environment;
+import freemarker.template.*;
+import org.nocturne.main.ApplicationContext;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ * Generates caption value by shortcut and args. It injected to any page or frame.
+ *
+ * Examples:
+ *
+ * {@literal <@caption params=["Mike"]>Hello, {0}@caption>}
+ * {@literal <@caption>Login@caption>}
+ * {@literal <@caption key="Login"/>}
+ * {@literal <@caption key="Hello, {0}" params=["Mike"]/>}
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@SuppressWarnings("Singleton")
+public class CaptionDirective implements TemplateDirectiveModel {
+ /**
+ * Singleton instance.
+ */
+ private static final CaptionDirective INSTANCE = new CaptionDirective();
+
+ private static final Object[] EMPTY_OBJECT_ARRAY = {};
+
+ private CaptionDirective() {
+ // No operations.
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ char[] chars = null;
+
+ if (body != null) {
+ CharArrayWriter writer = new CharArrayWriter();
+ body.render(writer);
+ writer.close();
+ chars = writer.toCharArray();
+ }
+
+ if (params.containsKey("key") && chars != null && chars.length > 0) {
+ throw new TemplateModelException(
+ "Caption directive expects key parameter or directive body, but not in the same time."
+ );
+ }
+
+ if (!params.containsKey("key") && (chars == null || chars.length == 0)) {
+ throw new TemplateModelException(
+ "Caption directive expects either key parameter or directive body, but none found."
+ );
+ }
+
+ Set keys = params.keySet();
+
+ String key;
+ if (params.containsKey("key")) {
+ key = params.get("key").toString();
+ } else {
+ key = String.valueOf(chars);
+ }
+ keys.remove("key");
+
+ Object p = params.get("params");
+ keys.remove("params");
+
+ if (!keys.isEmpty()) {
+ throw new TemplateModelException("Caption directive contains unexpected params.");
+ }
+
+ Object[] args = EMPTY_OBJECT_ARRAY;
+
+ if (p instanceof SimpleSequence) {
+ SimpleSequence sequence = (SimpleSequence) p;
+ args = new Object[sequence.size()];
+ for (int i = 0; i < sequence.size(); i++) {
+ Object arg = sequence.get(i);
+ if (arg instanceof SimpleNumber) {
+ args[i] = ((TemplateNumberModel) arg).getAsNumber();
+ } else {
+ args[i] = arg.toString();
+ }
+ }
+ }
+
+ String value = ApplicationContext.getInstance().$(key, args);
+ env.getOut().write(value);
+ }
+
+ /**
+ * @return Returns singleton instance.
+ */
+ public static CaptionDirective getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/code/src/main/java/org/nocturne/caption/Captions.java b/code/src/main/java/org/nocturne/caption/Captions.java
index d27b3c9..ac91c9b 100644
--- a/code/src/main/java/org/nocturne/caption/Captions.java
+++ b/code/src/main/java/org/nocturne/caption/Captions.java
@@ -1,44 +1,44 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.caption;
-
-import java.util.Locale;
-
-/**
- *
- * Interface through which works ApplicationContext.$() method,
- * "{{shortcut}}" and {@literal <@caption/>} directive in templates.
- *
- * It is native method in nocturne to write internationalized
- * application. Just write in templates "{{some text}}".
- *
- * @author Mike Mirzayanov
- */
-public interface Captions {
- /**
- * Returns caption value.
- *
- * @param locale Locale for which caption value to find.
- * @param shortcut Caption shortcut.
- * @param args of type Arguments (if value has placeholders like "Hello, {0}").
- * @return String Caption value.
- */
- String find(Locale locale, String shortcut, Object... args);
-
-
- /**
- * Returns caption value. Uses current application locale (see ApplicationContext.getInstance().getLocale()).
- * Typical implementation is
- *
- * public String find(String shortcut, Object... args) {
- * return find(ApplicationContext.getInstance().getLocale(), shortcut, args);
- * }
- *
- *
- * @param shortcut Caption shortcut.
- * @param args of type Arguments (if value has placeholders like "Hello, {0}").
- * @return String Caption value.
- */
- String find(String shortcut, Object... args);
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.caption;
+
+import java.util.Locale;
+
+/**
+ *
+ * Interface through which works ApplicationContext.$() method,
+ * "{{shortcut}}" and {@literal <@caption/>} directive in templates.
+ *
+ * It is native method in nocturne to write internationalized
+ * application. Just write in templates "{{some text}}".
+ *
+ * @author Mike Mirzayanov
+ */
+public interface Captions {
+ /**
+ * Returns caption value.
+ *
+ * @param locale Locale for which caption value to find.
+ * @param shortcut Caption shortcut.
+ * @param args of type Arguments (if value has placeholders like "Hello, {0}").
+ * @return String Caption value.
+ */
+ String find(Locale locale, String shortcut, Object... args);
+
+
+ /**
+ * Returns caption value. Uses current application locale (see ApplicationContext.getInstance().getLocale()).
+ * Typical implementation is
+ *
+ * public String find(String shortcut, Object... args) {
+ * return find(ApplicationContext.getInstance().getLocale(), shortcut, args);
+ * }
+ *
+ *
+ * @param shortcut Caption shortcut.
+ * @param args of type Arguments (if value has placeholders like "Hello, {0}").
+ * @return String Caption value.
+ */
+ String find(String shortcut, Object... args);
+}
diff --git a/code/src/main/java/org/nocturne/caption/CaptionsImpl.java b/code/src/main/java/org/nocturne/caption/CaptionsImpl.java
index daa8d28..ca62880 100644
--- a/code/src/main/java/org/nocturne/caption/CaptionsImpl.java
+++ b/code/src/main/java/org/nocturne/caption/CaptionsImpl.java
@@ -1,226 +1,226 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.caption;
-
-import com.google.inject.Singleton;
-import org.apache.log4j.Logger;
-import org.nocturne.exception.ConfigurationException;
-import org.nocturne.main.ApplicationContext;
-
-import java.io.*;
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- *
- * Simple implementation of Captions interface. Uses properties files to store values.
- *
- *
- * In the development mode it try to locate them in the directory
- * ApplicationContext.getInstance().getDebugCaptionsDir() (see nocturne.debug-captions-dir).
- * It modifies all of them if finds new caption shortcut and saves values nocturne.null except
- * for default language which will have value equals to shortcut.
- *
- *
- * In the production mode it just read them exactly once (on startup) and doesn't save them.
- *
- *
- * @author Mike Mirzayanov
- */
-@Singleton
-public class CaptionsImpl implements Captions {
- private static final Logger logger = Logger.getLogger(CaptionsImpl.class);
-
- private static final Pattern CAPTIONS_FILE_PATTERN = Pattern.compile("captions_[\\w]{2}\\.properties");
-
- /**
- * Stores properties per language.
- */
- private final Map propertiesMap = new Hashtable<>();
-
- /**
- * Magic value to store empty value.
- */
- private static final String NULL = "nocturne.null";
-
- /**
- * Constructs new CaptionsImpl.
- */
- public CaptionsImpl() {
- // Load properties on startup.
- loadProperties();
- }
-
- @Override
- public String find(String shortcut, Object... args) {
- return find(ApplicationContext.getInstance().getLocale(), shortcut, args);
- }
-
- @Override
- public String find(Locale locale, String shortcut, Object... args) {
- // Default locale. It is possible equals with current "locale".
- Locale defaultLocale = ApplicationContext.getInstance().getDefaultLocale();
-
- // Current language.
- String language = locale.getLanguage();
-
- // If doesn't contain locale properties?
- if (!propertiesMap.containsKey(language)) {
- // Add empty properties.
- propertiesMap.put(language, new Properties());
- }
-
- // It exists anyway.
- Properties properties = propertiesMap.get(language);
-
- // It can be absent.
- String value = properties.getProperty(shortcut);
-
- // No such value?
- if (value == null || value.equals(NULL)) {
- // Is it NOT default locale?
- if (defaultLocale == locale) {
- // Set default.
- properties.setProperty(shortcut, shortcut);
- }
- // Use default locale to find value.
- value = find(defaultLocale, shortcut, args);
- // Save all properties.
- if (ApplicationContext.getInstance().isDebug()) {
- saveProperties();
- }
- }
-
- if (args.length > 0) {
- return MessageFormat.format(value, args);
- } else {
- return value;
- }
- }
-
- /**
- * Synchronizes all the properties and saves them.
- */
- private void saveProperties() {
- // Find all possible keys.
- Set keys = new TreeSet<>();
- for (Map.Entry entry : propertiesMap.entrySet()) {
- Set names = entry.getValue().keySet();
- for (Object name : names) {
- keys.add(name.toString());
- }
- }
-
- // Add empty value for each key if no such found.
- for (Map.Entry entry : propertiesMap.entrySet()) {
- Properties properties = entry.getValue();
- for (String key : keys) {
- if (!properties.containsKey(key)) {
- properties.setProperty(key, NULL);
- }
- }
- // And save properties.
- save(properties, entry.getKey());
- }
- }
-
- /**
- * @param properties Properties to be saved in the file.
- * @param language Language.
- */
- private static void save(Properties properties, String language) {
- File file = new File(ApplicationContext.getInstance().getDebugCaptionsDir(), getCaptionsFileName(language));
- try {
- Writer writer = new OutputStreamWriter(new FileOutputStream(file), ApplicationContext.getInstance().getCaptionFilesEncoding());
- properties.store(writer, null);
- writer.close();
- } catch (IOException e) {
- logger.error("Can't write into file " + file + '.', e);
- throw new ConfigurationException("Can't write into file " + file + '.', e);
- }
- }
-
- /**
- * Method loadProperties ...
- */
- private void loadProperties() {
- if (ApplicationContext.getInstance().isDebug()) {
- loadPropertiesForDebug();
- } else {
- loadPropertiesForProduction();
- }
- }
-
- /**
- * Method loadPropertiesForProduction ...
- */
- private void loadPropertiesForProduction() {
- if (propertiesMap.isEmpty()) {
- List languages = ApplicationContext.getInstance().getAllowedLanguages();
- for (String language : languages) {
- InputStream inputStream = getClass().getResourceAsStream(getCaptionsFileName(language));
- if (inputStream != null) {
- try {
- Reader reader = new InputStreamReader(inputStream, ApplicationContext.getInstance().getCaptionFilesEncoding());
- Properties properties = new Properties();
- properties.load(reader);
- reader.close();
- propertiesMap.put(language, properties);
- } catch (IOException e) {
- logger.error("Can't load caption properties for language " + language + '.', e);
- throw new ConfigurationException("Can't load caption properties for language " + language + '.', e);
- }
- }
- }
- }
- }
-
- /**
- * Method loadPropertiesForDebug ...
- */
- private void loadPropertiesForDebug() {
- File debugCaptionsDir = new File(ApplicationContext.getInstance().getDebugCaptionsDir());
-
- File[] captionFiles = debugCaptionsDir.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return CAPTIONS_FILE_PATTERN.matcher(name).matches();
- }
- });
-
- propertiesMap.clear();
-
- for (File captionFile : captionFiles) {
- if (captionFile.isFile()) {
- Pattern pattern = Pattern.compile("captions_([\\w]{2})\\.properties");
- Matcher matcher = pattern.matcher(captionFile.getName());
- if (matcher.matches()) {
- String language = matcher.group(1);
- try {
- Reader reader = new InputStreamReader(new FileInputStream(captionFile),
- ApplicationContext.getInstance().getCaptionFilesEncoding());
- Properties properties = new Properties();
- properties.load(reader);
- propertiesMap.put(language, properties);
- reader.close();
- } catch (IOException ignored) {
- // No operations.
- }
- }
- }
- }
- }
-
- /**
- * Method getCaptionsFileName ...
- *
- * @param language of type String
- * @return String
- */
- private static String getCaptionsFileName(String language) {
- return "/captions_" + language + ".properties";
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.caption;
+
+import com.google.inject.Singleton;
+import org.apache.log4j.Logger;
+import org.nocturne.exception.ConfigurationException;
+import org.nocturne.main.ApplicationContext;
+
+import java.io.*;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * Simple implementation of Captions interface. Uses properties files to store values.
+ *
+ *
+ * In the development mode it try to locate them in the directory
+ * ApplicationContext.getInstance().getDebugCaptionsDir() (see nocturne.debug-captions-dir).
+ * It modifies all of them if finds new caption shortcut and saves values nocturne.null except
+ * for default language which will have value equals to shortcut.
+ *
+ *
+ * In the production mode it just read them exactly once (on startup) and doesn't save them.
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+@Singleton
+public class CaptionsImpl implements Captions {
+ private static final Logger logger = Logger.getLogger(CaptionsImpl.class);
+
+ private static final Pattern CAPTIONS_FILE_PATTERN = Pattern.compile("captions_[\\w]{2}\\.properties");
+
+ /**
+ * Stores properties per language.
+ */
+ private final Map propertiesMap = new Hashtable<>();
+
+ /**
+ * Magic value to store empty value.
+ */
+ private static final String NULL = "nocturne.null";
+
+ /**
+ * Constructs new CaptionsImpl.
+ */
+ public CaptionsImpl() {
+ // Load properties on startup.
+ loadProperties();
+ }
+
+ @Override
+ public String find(String shortcut, Object... args) {
+ return find(ApplicationContext.getInstance().getLocale(), shortcut, args);
+ }
+
+ @Override
+ public String find(Locale locale, String shortcut, Object... args) {
+ // Default locale. It is possible equals with current "locale".
+ Locale defaultLocale = ApplicationContext.getInstance().getDefaultLocale();
+
+ // Current language.
+ String language = locale.getLanguage();
+
+ // If doesn't contain locale properties?
+ if (!propertiesMap.containsKey(language)) {
+ // Add empty properties.
+ propertiesMap.put(language, new Properties());
+ }
+
+ // It exists anyway.
+ Properties properties = propertiesMap.get(language);
+
+ // It can be absent.
+ String value = properties.getProperty(shortcut);
+
+ // No such value?
+ if (value == null || value.equals(NULL)) {
+ // Is it NOT default locale?
+ if (defaultLocale == locale) {
+ // Set default.
+ properties.setProperty(shortcut, shortcut);
+ }
+ // Use default locale to find value.
+ value = find(defaultLocale, shortcut, args);
+ // Save all properties.
+ if (ApplicationContext.getInstance().isDebug()) {
+ saveProperties();
+ }
+ }
+
+ if (args.length > 0) {
+ return MessageFormat.format(value, args);
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Synchronizes all the properties and saves them.
+ */
+ private void saveProperties() {
+ // Find all possible keys.
+ Set keys = new TreeSet<>();
+ for (Map.Entry entry : propertiesMap.entrySet()) {
+ Set names = entry.getValue().keySet();
+ for (Object name : names) {
+ keys.add(name.toString());
+ }
+ }
+
+ // Add empty value for each key if no such found.
+ for (Map.Entry entry : propertiesMap.entrySet()) {
+ Properties properties = entry.getValue();
+ for (String key : keys) {
+ if (!properties.containsKey(key)) {
+ properties.setProperty(key, NULL);
+ }
+ }
+ // And save properties.
+ save(properties, entry.getKey());
+ }
+ }
+
+ /**
+ * @param properties Properties to be saved in the file.
+ * @param language Language.
+ */
+ private static void save(Properties properties, String language) {
+ File file = new File(ApplicationContext.getInstance().getDebugCaptionsDir(), getCaptionsFileName(language));
+ try {
+ Writer writer = new OutputStreamWriter(new FileOutputStream(file), ApplicationContext.getInstance().getCaptionFilesEncoding());
+ properties.store(writer, null);
+ writer.close();
+ } catch (IOException e) {
+ logger.error("Can't write into file " + file + '.', e);
+ throw new ConfigurationException("Can't write into file " + file + '.', e);
+ }
+ }
+
+ /**
+ * Method loadProperties ...
+ */
+ private void loadProperties() {
+ if (ApplicationContext.getInstance().isDebug()) {
+ loadPropertiesForDebug();
+ } else {
+ loadPropertiesForProduction();
+ }
+ }
+
+ /**
+ * Method loadPropertiesForProduction ...
+ */
+ private void loadPropertiesForProduction() {
+ if (propertiesMap.isEmpty()) {
+ List languages = ApplicationContext.getInstance().getAllowedLanguages();
+ for (String language : languages) {
+ InputStream inputStream = getClass().getResourceAsStream(getCaptionsFileName(language));
+ if (inputStream != null) {
+ try {
+ Reader reader = new InputStreamReader(inputStream, ApplicationContext.getInstance().getCaptionFilesEncoding());
+ Properties properties = new Properties();
+ properties.load(reader);
+ reader.close();
+ propertiesMap.put(language, properties);
+ } catch (IOException e) {
+ logger.error("Can't load caption properties for language " + language + '.', e);
+ throw new ConfigurationException("Can't load caption properties for language " + language + '.', e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Method loadPropertiesForDebug ...
+ */
+ private void loadPropertiesForDebug() {
+ File debugCaptionsDir = new File(ApplicationContext.getInstance().getDebugCaptionsDir());
+
+ File[] captionFiles = debugCaptionsDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return CAPTIONS_FILE_PATTERN.matcher(name).matches();
+ }
+ });
+
+ propertiesMap.clear();
+
+ for (File captionFile : captionFiles) {
+ if (captionFile.isFile()) {
+ Pattern pattern = Pattern.compile("captions_([\\w]{2})\\.properties");
+ Matcher matcher = pattern.matcher(captionFile.getName());
+ if (matcher.matches()) {
+ String language = matcher.group(1);
+ try {
+ Reader reader = new InputStreamReader(new FileInputStream(captionFile),
+ ApplicationContext.getInstance().getCaptionFilesEncoding());
+ Properties properties = new Properties();
+ properties.load(reader);
+ propertiesMap.put(language, properties);
+ reader.close();
+ } catch (IOException ignored) {
+ // No operations.
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Method getCaptionsFileName ...
+ *
+ * @param language of type String
+ * @return String
+ */
+ private static String getCaptionsFileName(String language) {
+ return "/captions_" + language + ".properties";
+ }
+}
diff --git a/code/src/main/java/org/nocturne/collection/SingleEntryList.java b/code/src/main/java/org/nocturne/collection/SingleEntryList.java
index f23af23..d4ecae2 100644
--- a/code/src/main/java/org/nocturne/collection/SingleEntryList.java
+++ b/code/src/main/java/org/nocturne/collection/SingleEntryList.java
@@ -1,383 +1,383 @@
-package org.nocturne.collection;
-
-import org.apache.commons.lang3.ArrayUtils;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.NotThreadSafe;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.lang.reflect.Array;
-import java.util.*;
-
-/**
- * @author Maxim Shipko (sladethe@gmail.com)
- * Date: 04.06.13
- */
-@SuppressWarnings({"NonSerializableFieldInSerializableClass", "CloneableClassInSecureContext", "DeserializableClassInSecureContext"})
-@NotThreadSafe
-public final class SingleEntryList implements List, RandomAccess, Cloneable, Serializable {
- private boolean hasValue;
-
- @Nullable
- private E value;
-
- public SingleEntryList() {
- // No operations.
- }
-
- public SingleEntryList(@Nullable E value) {
- this.hasValue = true;
- this.value = value;
- }
-
- @SuppressWarnings("WeakerAccess")
- public SingleEntryList(Collection collection) {
- addAll(collection);
- }
-
- @Override
- public int size() {
- return hasValue ? 1 : 0;
- }
-
- @Override
- public boolean isEmpty() {
- return !hasValue;
- }
-
- @Override
- public boolean contains(Object o) {
- return hasValue && (o == null ? value == null : o.equals(value));
- }
-
- @Nonnull
- @Override
- public Iterator iterator() {
- return new SingleEntryListIterator<>(this);
- }
-
- @Nonnull
- @Override
- public Object[] toArray() {
- return hasValue ? new Object[]{value} : ArrayUtils.EMPTY_OBJECT_ARRAY;
- }
-
- @SuppressWarnings("unchecked")
- @Nonnull
- @Override
- public T[] toArray(@Nonnull T[] a) {
- int arrayLength = a.length;
-
- if (hasValue) {
- if (arrayLength > 0) {
- try {
- a[0] = (T) value;
- } catch (ClassCastException e) {
- throw new ArrayStoreException(e.getLocalizedMessage());
- }
-
- for (int arrayIndex = 1; arrayIndex < arrayLength; ++arrayIndex) {
- a[arrayIndex] = null;
- }
-
- return a;
- } else {
- a = (T[]) Array.newInstance(a.getClass().getComponentType(), 1);
-
- try {
- a[0] = (T) value;
- } catch (ClassCastException e) {
- throw new ArrayStoreException(e.getLocalizedMessage());
- }
-
- return a;
- }
- } else {
- for (int arrayIndex = 0; arrayIndex < arrayLength; ++arrayIndex) {
- a[arrayIndex] = null;
- }
- return a;
- }
- }
-
- @Override
- public boolean add(E e) {
- if (hasValue) {
- throw new IllegalStateException("Can't add more than one element to the list.");
- } else {
- hasValue = true;
- value = e;
- return true;
- }
- }
-
- @Override
- public boolean remove(Object o) {
- if (hasValue) {
- if (contains(o)) {
- hasValue = false;
- value = null;
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
-
- @Override
- public boolean containsAll(@Nonnull Collection> collection) {
- for (Object o : collection) {
- if (!contains(o)) {
- return false;
- }
- }
-
- return true;
- }
-
- @Override
- public boolean addAll(@Nonnull Collection extends E> collection) {
- boolean changed = false;
-
- for (E e : collection) {
- changed |= add(e);
- }
-
- return changed;
- }
-
- @Override
- public boolean addAll(int index, @Nonnull Collection extends E> collection) {
- if (index == 0) {
- return addAll(collection);
- } else {
- throw new IllegalStateException("List size is limited to 1.");
- }
- }
-
- @Override
- public boolean removeAll(@Nonnull Collection> collection) {
- boolean changed = false;
-
- for (Object o : collection) {
- changed |= remove(o);
- }
-
- return changed;
- }
-
- @Override
- public boolean retainAll(@Nonnull Collection> collection) {
- if (hasValue) {
- if (collection.contains(value)) {
- return false;
- } else {
- hasValue = false;
- value = null;
- return true;
- }
- } else {
- return false;
- }
- }
-
- @Override
- public void clear() {
- hasValue = false;
- value = null;
- }
-
- @SuppressWarnings("ConstantConditions")
- @Override
- public E get(int index) {
- if (hasValue && index == 0) {
- return value;
- }
-
- throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
- }
-
- @SuppressWarnings("ConstantConditions")
- @Override
- public E set(int index, E element) {
- if (hasValue && index == 0) {
- E previousValue = value;
- value = element;
- return previousValue;
- }
-
- throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
- }
-
- @Override
- public void add(int index, E element) {
- if (index == 0) {
- if (hasValue) {
- throw new IllegalStateException("Can't add more than one element to the list.");
- } else {
- hasValue = true;
- value = element;
- }
- } else {
- throw new IllegalStateException("List size is limited to 1.");
- }
- }
-
- @SuppressWarnings("ConstantConditions")
- @Override
- public E remove(int index) {
- if (hasValue && index == 0) {
- E previousValue = value;
- hasValue = false;
- value = null;
- return previousValue;
- }
-
- throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
- }
-
- @Override
- public int indexOf(Object o) {
- return contains(o) ? 0 : -1;
- }
-
- @Override
- public int lastIndexOf(Object o) {
- return contains(o) ? 0 : -1;
- }
-
- @Nonnull
- @Override
- public ListIterator listIterator() {
- return new SingleEntryListIterator<>(this);
- }
-
- @Nonnull
- @Override
- public ListIterator listIterator(int index) {
- return new SingleEntryListIterator<>(this, index);
- }
-
- @Nonnull
- @Override
- public List subList(int fromIndex, int toIndex) {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneCallsConstructors"})
- @Override
- public SingleEntryList clone() {
- return new SingleEntryList<>(this);
- }
-
- private void writeObject(ObjectOutputStream outputStream) throws IOException {
- outputStream.defaultWriteObject();
- }
-
- private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
- inputStream.defaultReadObject();
- }
-
- private static final class SingleEntryListIterator implements ListIterator {
- private final SingleEntryList singleEntryList;
-
- private int currentIndex;
- private int lastReturnedIndex = -1;
-
- private SingleEntryListIterator(SingleEntryList singleEntryList) {
- this.singleEntryList = singleEntryList;
- }
-
- private SingleEntryListIterator(SingleEntryList singleEntryList, int index) {
- if (index != 0) {
- throw new IllegalStateException("List size is limited to 1.");
- }
-
- this.singleEntryList = singleEntryList;
- this.currentIndex = index;
- }
-
- @Override
- public boolean hasNext() {
- return currentIndex != singleEntryList.size();
- }
-
- @Override
- public E next() {
- if (!hasNext()) {
- throw new NoSuchElementException("There is no next element.");
- }
-
- E element = singleEntryList.get(currentIndex);
- lastReturnedIndex = currentIndex;
- ++currentIndex;
- return element;
- }
-
- @Override
- public boolean hasPrevious() {
- return currentIndex != 0;
- }
-
- @Override
- public E previous() {
- if (!hasPrevious()) {
- throw new NoSuchElementException("There is no previous element.");
- }
-
- --currentIndex;
- E element = singleEntryList.get(currentIndex);
- lastReturnedIndex = currentIndex;
- return element;
- }
-
- @Override
- public int nextIndex() {
- return currentIndex;
- }
-
- @Override
- public int previousIndex() {
- return currentIndex - 1;
- }
-
- @Override
- public void remove() {
- if (lastReturnedIndex == -1) {
- throw new IllegalStateException("There is no element to remove.");
- }
-
- singleEntryList.remove(lastReturnedIndex);
-
- if (lastReturnedIndex < currentIndex) {
- --currentIndex;
- }
-
- lastReturnedIndex = -1;
- }
-
- @Override
- public void set(E e) {
- if (lastReturnedIndex == -1) {
- throw new IllegalStateException("There is no element to set.");
- }
-
- singleEntryList.set(lastReturnedIndex, e);
- }
-
- @Override
- public void add(E e) {
- if (singleEntryList.isEmpty() && currentIndex == 0) {
- singleEntryList.add(currentIndex++, e);
- lastReturnedIndex = -1;
- } else {
- throw new IllegalStateException("Can't add more than one element to the list.");
- }
- }
- }
-}
+package org.nocturne.collection;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * @author Maxim Shipko (sladethe@gmail.com)
+ * Date: 04.06.13
+ */
+@SuppressWarnings({"NonSerializableFieldInSerializableClass", "CloneableClassInSecureContext", "DeserializableClassInSecureContext"})
+@NotThreadSafe
+public final class SingleEntryList implements List, RandomAccess, Cloneable, Serializable {
+ private boolean hasValue;
+
+ @Nullable
+ private E value;
+
+ public SingleEntryList() {
+ // No operations.
+ }
+
+ public SingleEntryList(@Nullable E value) {
+ this.hasValue = true;
+ this.value = value;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public SingleEntryList(Collection collection) {
+ addAll(collection);
+ }
+
+ @Override
+ public int size() {
+ return hasValue ? 1 : 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return !hasValue;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return hasValue && (o == null ? value == null : o.equals(value));
+ }
+
+ @Nonnull
+ @Override
+ public Iterator iterator() {
+ return new SingleEntryListIterator<>(this);
+ }
+
+ @Nonnull
+ @Override
+ public Object[] toArray() {
+ return hasValue ? new Object[]{value} : ArrayUtils.EMPTY_OBJECT_ARRAY;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nonnull
+ @Override
+ public T[] toArray(@Nonnull T[] a) {
+ int arrayLength = a.length;
+
+ if (hasValue) {
+ if (arrayLength > 0) {
+ try {
+ a[0] = (T) value;
+ } catch (ClassCastException e) {
+ throw new ArrayStoreException(e.getLocalizedMessage());
+ }
+
+ for (int arrayIndex = 1; arrayIndex < arrayLength; ++arrayIndex) {
+ a[arrayIndex] = null;
+ }
+
+ return a;
+ } else {
+ a = (T[]) Array.newInstance(a.getClass().getComponentType(), 1);
+
+ try {
+ a[0] = (T) value;
+ } catch (ClassCastException e) {
+ throw new ArrayStoreException(e.getLocalizedMessage());
+ }
+
+ return a;
+ }
+ } else {
+ for (int arrayIndex = 0; arrayIndex < arrayLength; ++arrayIndex) {
+ a[arrayIndex] = null;
+ }
+ return a;
+ }
+ }
+
+ @Override
+ public boolean add(E e) {
+ if (hasValue) {
+ throw new IllegalStateException("Can't add more than one element to the list.");
+ } else {
+ hasValue = true;
+ value = e;
+ return true;
+ }
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (hasValue) {
+ if (contains(o)) {
+ hasValue = false;
+ value = null;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean containsAll(@Nonnull Collection> collection) {
+ for (Object o : collection) {
+ if (!contains(o)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean addAll(@Nonnull Collection extends E> collection) {
+ boolean changed = false;
+
+ for (E e : collection) {
+ changed |= add(e);
+ }
+
+ return changed;
+ }
+
+ @Override
+ public boolean addAll(int index, @Nonnull Collection extends E> collection) {
+ if (index == 0) {
+ return addAll(collection);
+ } else {
+ throw new IllegalStateException("List size is limited to 1.");
+ }
+ }
+
+ @Override
+ public boolean removeAll(@Nonnull Collection> collection) {
+ boolean changed = false;
+
+ for (Object o : collection) {
+ changed |= remove(o);
+ }
+
+ return changed;
+ }
+
+ @Override
+ public boolean retainAll(@Nonnull Collection> collection) {
+ if (hasValue) {
+ if (collection.contains(value)) {
+ return false;
+ } else {
+ hasValue = false;
+ value = null;
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void clear() {
+ hasValue = false;
+ value = null;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public E get(int index) {
+ if (hasValue && index == 0) {
+ return value;
+ }
+
+ throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public E set(int index, E element) {
+ if (hasValue && index == 0) {
+ E previousValue = value;
+ value = element;
+ return previousValue;
+ }
+
+ throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
+ }
+
+ @Override
+ public void add(int index, E element) {
+ if (index == 0) {
+ if (hasValue) {
+ throw new IllegalStateException("Can't add more than one element to the list.");
+ } else {
+ hasValue = true;
+ value = element;
+ }
+ } else {
+ throw new IllegalStateException("List size is limited to 1.");
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public E remove(int index) {
+ if (hasValue && index == 0) {
+ E previousValue = value;
+ hasValue = false;
+ value = null;
+ return previousValue;
+ }
+
+ throw new IndexOutOfBoundsException("index=" + index + ", size=" + size() + '.');
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return contains(o) ? 0 : -1;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return contains(o) ? 0 : -1;
+ }
+
+ @Nonnull
+ @Override
+ public ListIterator listIterator() {
+ return new SingleEntryListIterator<>(this);
+ }
+
+ @Nonnull
+ @Override
+ public ListIterator listIterator(int index) {
+ return new SingleEntryListIterator<>(this, index);
+ }
+
+ @Nonnull
+ @Override
+ public List subList(int fromIndex, int toIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneCallsConstructors"})
+ @Override
+ public SingleEntryList clone() {
+ return new SingleEntryList<>(this);
+ }
+
+ private void writeObject(ObjectOutputStream outputStream) throws IOException {
+ outputStream.defaultWriteObject();
+ }
+
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ }
+
+ private static final class SingleEntryListIterator implements ListIterator {
+ private final SingleEntryList singleEntryList;
+
+ private int currentIndex;
+ private int lastReturnedIndex = -1;
+
+ private SingleEntryListIterator(SingleEntryList singleEntryList) {
+ this.singleEntryList = singleEntryList;
+ }
+
+ private SingleEntryListIterator(SingleEntryList singleEntryList, int index) {
+ if (index != 0) {
+ throw new IllegalStateException("List size is limited to 1.");
+ }
+
+ this.singleEntryList = singleEntryList;
+ this.currentIndex = index;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return currentIndex != singleEntryList.size();
+ }
+
+ @Override
+ public E next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException("There is no next element.");
+ }
+
+ E element = singleEntryList.get(currentIndex);
+ lastReturnedIndex = currentIndex;
+ ++currentIndex;
+ return element;
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return currentIndex != 0;
+ }
+
+ @Override
+ public E previous() {
+ if (!hasPrevious()) {
+ throw new NoSuchElementException("There is no previous element.");
+ }
+
+ --currentIndex;
+ E element = singleEntryList.get(currentIndex);
+ lastReturnedIndex = currentIndex;
+ return element;
+ }
+
+ @Override
+ public int nextIndex() {
+ return currentIndex;
+ }
+
+ @Override
+ public int previousIndex() {
+ return currentIndex - 1;
+ }
+
+ @Override
+ public void remove() {
+ if (lastReturnedIndex == -1) {
+ throw new IllegalStateException("There is no element to remove.");
+ }
+
+ singleEntryList.remove(lastReturnedIndex);
+
+ if (lastReturnedIndex < currentIndex) {
+ --currentIndex;
+ }
+
+ lastReturnedIndex = -1;
+ }
+
+ @Override
+ public void set(E e) {
+ if (lastReturnedIndex == -1) {
+ throw new IllegalStateException("There is no element to set.");
+ }
+
+ singleEntryList.set(lastReturnedIndex, e);
+ }
+
+ @Override
+ public void add(E e) {
+ if (singleEntryList.isEmpty() && currentIndex == 0) {
+ singleEntryList.add(currentIndex++, e);
+ lastReturnedIndex = -1;
+ } else {
+ throw new IllegalStateException("Can't add more than one element to the list.");
+ }
+ }
+ }
+}
diff --git a/code/src/main/java/org/nocturne/ddos/PowFilter.java b/code/src/main/java/org/nocturne/ddos/PowFilter.java
index 7cfe6fb..cb2e0fe 100644
--- a/code/src/main/java/org/nocturne/ddos/PowFilter.java
+++ b/code/src/main/java/org/nocturne/ddos/PowFilter.java
@@ -1,429 +1,429 @@
-package org.nocturne.ddos;
-
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.log4j.Logger;
-import org.nocturne.util.StringUtil;
-
-import javax.servlet.*;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.security.SecureRandom;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
-@SuppressWarnings("unused")
-public class PowFilter implements Filter {
- private static final Logger logger = Logger.getLogger(PowFilter.class);
-
- private static final String X_REAL_IP = "X-Real-IP";
-
- private static final boolean logging = System.getProperty("PowFilter.logging", "false").equals("true");
-
- private static final Random RANDOM = new SecureRandom(Long.toString(System.nanoTime()
- ^ System.currentTimeMillis()
- ^ Runtime.getRuntime().freeMemory()).getBytes(StandardCharsets.UTF_8));
-
- private static final List REQUEST_FILTERS = new ArrayList<>();
-
- private static final ThreadLocal rayIdLocal = new ThreadLocal<>();
-
- @Override
- public void init(FilterConfig filterConfig) {
- // No operations.
- }
-
- @Override
- public void destroy() {
- // No operations.
- }
-
- private void info(String message) {
- if (logging) {
- message = "PowFilter: powRayId=" + rayIdLocal.get() + ": " + message;
- logger.info(message);
-
- String print = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ": " + message;
- System.out.println(print);
- System.err.println(print);
- }
- }
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
-
- String rayId = RandomStringUtils.randomAlphanumeric(8);
- rayIdLocal.set(rayId);
-
- info("Starting processing request [uri=" + httpServletRequest.getRequestURI()
- + ", url=" + httpServletRequest.getRequestURL()
- + ", query=" + httpServletRequest.getQueryString()
- + ", ip=" + getIp(httpServletRequest) + "].");
-
- if (logging) {
- info("Headers:");
- Enumeration headerNames = httpServletRequest.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String headerName = headerNames.nextElement();
- info(" " + headerName + ": " + httpServletRequest.getHeader(headerName));
- }
-
- HttpSession session = httpServletRequest.getSession(true);
- if (session != null) {
- info("Session attributes:");
- Enumeration attributeNames = session.getAttributeNames();
- while (attributeNames.hasMoreElements()) {
- String attributeName = attributeNames.nextElement();
- info(" " + attributeName + ": " + session.getAttribute(attributeName));
- }
- }
-
- info("Cookies:");
- Cookie[] cookies = httpServletRequest.getCookies();
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- info(" " + cookie.getName() + ": " + cookie.getValue());
- }
- }
- }
-
- for (RequestFilter requestFilter : REQUEST_FILTERS) {
- Integer verdict = requestFilter.filter(httpServletRequest);
- info("Request filter " + requestFilter.getClass().getSimpleName() + " returned " + verdict + ".");
-
- if (verdict != null) {
- if (verdict == 0) {
- info("Do 'chain.doFilter(request, response);' and return.");
- chain.doFilter(request, response);
- } else {
- info("Send error " + verdict + " and return.");
- httpServletResponse.sendError(verdict);
- }
- return;
- }
- }
-
- doInternalFilter(httpServletRequest, httpServletResponse, chain);
- } else {
- chain.doFilter(request, response);
- }
- }
-
- private static String getIp(HttpServletRequest httpRequest) {
- String ip = httpRequest.getHeader(X_REAL_IP);
- if (StringUtil.isNotEmpty(ip)) {
- return ip;
- } else {
- return httpRequest.getRemoteAddr();
- }
- }
-
- private static String getUserAgent(HttpServletRequest httpRequest) {
- return httpRequest.getHeader("User-Agent");
- }
-
- private static String getRequestFingerprint(HttpServletRequest request) {
- return "#" + getIp(request) + "!" + getUserAgent(request);
- }
-
- private void doInternalFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- HttpSession session = request.getSession();
-
- String secret = (String) session.getAttribute("secret");
- String sha = (String) session.getAttribute("sha");
- String requestFingerprint = getRequestFingerprint(request);
-
- info("sessionId= " + session.getId()
- + ", secret=" + secret
- + ", sha=" + sha
- + ", requestFingerprint=" + requestFingerprint + ".");
-
- if (StringUtil.isEmpty(secret)
- || StringUtil.isEmpty(sha)
- || !sha.equals(DigestUtils.sha1Hex(secret + requestFingerprint))) {
- secret = nextSecret();
- session.setAttribute("secret", secret);
- sha = DigestUtils.sha1Hex(secret + requestFingerprint);
- session.setAttribute("sha", sha);
- info("If empty case: secret=" + secret + ", sha=" + sha + ".");
- }
-
- String half = sha.substring(0, 20);
- String cookie = null;
- if (request.getCookies() != null) {
- for (Cookie c : request.getCookies()) {
- if (c.getName().equals("pow")) {
- cookie = c.getValue();
- break;
- }
- }
- }
-
- info("half=" + half + ", cookie=" + cookie + ".");
-
- if (cookie != null && cookie.equals(sha)) {
- info("cookie != null && cookie.equals(sha).");
- chain.doFilter(request, response);
- } else if (cookie != null && isResult(cookie, half)) {
- info("cookie != null && isResult(cookie, half): cookie=" + cookie + ", half=" + half + ".");
- Cookie powCookie = new Cookie("pow", sha);
- powCookie.setPath("/");
- powCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(1));
- response.addCookie(powCookie);
- info("Set-Cookie: pow=" + sha + ".");
- chain.doFilter(request, response);
- } else {
- info("else case: cookie=" + cookie + ", half=" + half + ".");
- Cookie powCookie = new Cookie("pow", half);
- powCookie.setPath("/");
- powCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(1));
- response.addCookie(powCookie);
- response.setContentType("text/html");
- printResponse(response);
- info("writer.flush(), Set-Cookie: pow=" + half + ".");
- }
- }
-
- private static void printResponse(HttpServletResponse response) throws IOException {
- PrintWriter writer = response.getWriter();
- writer.println("\nPlease wait. Your browser is being checked. It may take a few seconds...
");
- writer.println("");
- writer.flush();
- }
-
- private boolean isResult(String cookie, String halfSecret) {
- if (StringUtil.isNotEmpty(cookie) && cookie.endsWith("_" + halfSecret)) {
- String hash = DigestUtils.sha1Hex(cookie);
- return hash.startsWith("0000");
- } else {
- return false;
- }
- }
-
- private synchronized static String nextSecret() {
- StringBuilder result = new StringBuilder();
- for (int i = 0; i < 4; i++) {
- result.append(RANDOM.nextInt());
- }
- return DigestUtils.sha1Hex(result.toString());
- }
-
- private static String getObfuscatedJsCode() {
- return "var _0x3e09e3=_0x4bf8;(function(_0x42d959,_0x647252){var _0x25aba4=_0x4bf8,_0x437db0=_0x42d959();while(!![]){try{var _0x8e1627=-parseInt(_0x25aba4(0xa7))/0x1*(-parseInt(_0x25aba4(0x74))/0x2)+-parseInt(_0x25aba4(0xac))/0x3*(-parseInt(_0x25aba4(0x80))/0x4)+parseInt(_0x25aba4(0x98))/0x5*(parseInt(_0x25aba4(0xaa))/0x6)+parseInt(_0x25aba4(0x9d))/0x7+-parseInt(_0x25aba4(0x8a))/0x8*(-parseInt(_0x25aba4(0xab))/0x9)+parseInt(_0x25aba4(0x91))/0xa*(-parseInt(_0x25aba4(0x89))/0xb)+parseInt(_0x25aba4(0x72))/0xc*(-parseInt(_0x25aba4(0x7a))/0xd);if(_0x8e1627===_0x647252)break;else _0x437db0['push'](_0x437db0['shift']());}catch(_0x5d7d1d){_0x437db0['push'](_0x437db0['shift']());}}}(_0x2d66,0x4a27d));var _cs=['s=',_0x3e09e3(0xb6),_0x3e09e3(0x9a),'1024','0',_0x3e09e3(0xad),_0x3e09e3(0x94),';',_0x3e09e3(0x97),'=',_0x3e09e3(0x70),'\\x20','_',_0x3e09e3(0xae),'\\x0a',';p','000','=/','By',';','+','e','rCa','nav',_0x3e09e3(0xb5),'lo','bx','bst','bs','St',_0x3e09e3(0x9b),'it','arC','6c','th','ch',_0x3e09e3(0xa9),'mC','uk',_0x3e09e3(0xa6),'-',_0x3e09e3(0xb2),_0x3e09e3(0x96),'eAt','o7r','lz','omC','su','tr','c9','ri','eA','vi',_0x3e09e3(0x71),_0x3e09e3(0xb0),'h',_0x3e09e3(0x9c),'n6','gth','xl','jp','t','b64',_0x3e09e3(0x73),'b8y','ym','0','st','Of','hu','v4','co','gt',_0x3e09e3(0x8c),'4v','Ti','30','2n',_0x3e09e3(0x78),'pu','s9',_0x3e09e3(0x6f),'By','mCh','st','ki','zone','&','spl',_0x3e09e3(0x95),_0x3e09e3(0x76),'xnf','f7v','gbs','set',_0x3e09e3(0xa5),_0x3e09e3(0x7d),'re','h3q','zx','ad',_0x3e09e3(0x8d),'uhd',_0x3e09e3(0x86),'gt','ng',_0x3e09e3(0xb3),_0x3e09e3(0x81),_0x3e09e3(0x7b),'efm','ye','Ch',_0x3e09e3(0xa3),_0x3e09e3(0x9f),'hf','for','1wy',_0x3e09e3(0x88),'ow',_0x3e09e3(0xad),'Lo','to',_0x3e09e3(0xb1),'le','5r',_0x3e09e3(0xaf),'wph','get','7f','y3','Co',_0x3e09e3(0xa1),'sd','ce',_0x3e09e3(0xa4),_0x3e09e3(0x99),'fr','h7','t2','7y','dex','7c',_0x3e09e3(0x92),_0x3e09e3(0x83),'rC',_0x3e09e3(0xb6),_0x3e09e3(0xb4),'dow','od',_0x3e09e3(0x84),_0x3e09e3(0x7f),_0x3e09e3(0x79),'math','de',_0x3e09e3(0xa8),_0x3e09e3(0x8f),'At',_0x3e09e3(0x77),_0x3e09e3(0x7c),'in',_0x3e09e3(0x75),_0x3e09e3(0x82),_0x3e09e3(0x85),'har','fz',_0x3e09e3(0xa0),'Id','pa','9h','we','sh','me','se','m9','om','toU',_0x3e09e3(0x93),'rCo','g','w2',_0x3e09e3(0x7e),_0x3e09e3(0x8b),'hm','+','9v','0s',_0x3e09e3(0x9e),'h4',_0x3e09e3(0x8e),_0x3e09e3(0xa2),'%','t3z','5dk','bn',_0x3e09e3(0x99),'len',_0x3e09e3(0x87),'ar','ji',_0x3e09e3(0x90)];function _0x2d66(){var _0x9a6442=['nfl','n9s','pla','83v','fro','cha','60hKQkDX','abs','loc','ath','get','1024','ire','342455dubHwB','time','pow','mkk','nea','11382oqgMyh','Cha','7ox','qny','TCS','lau','win','geo','func','gs8','16181BJNMSb','Cod','sls','48axsGzc','95094eEcjTt','3CeVykH','Ele','exp','801','8jn','p66','tion','oki','sgg','0fi','pop','smp','nav','68m','60zvMLPs','rin','42qVGOUy','djt','ment','pus','ode','while','2725346qegrXi','sub','9ig','16u','tri','Str','392408ydMhjj','8a5','5i0','toS','pc2','nci','coo','l5u','irs','125972wOBRSc','328tXaqCx'];_0x2d66=function(){return _0x9a6442;};return _0x2d66();}function _f6(_0x2a5a76){function _0x2c4077(_0x4cb62a,_0x58288a){return _0x4cb62a<<_0x58288a|_0x4cb62a>>>0x20-_0x58288a;}function _0x22e96f(_0x1a925f){var _0x6b87c1='',_0x3b5310,_0x6446c7,_0x3a0707;for(_0x3b5310=0x0;_0x3b5310<=0x6;_0x3b5310+=0x2){_0x6446c7=_0x1a925f>>>_0x3b5310*0x4+0x4&0xf,_0x3a0707=_0x1a925f>>>_0x3b5310*0x4&0xf,_0x6b87c1+=_0x6446c7[_cs[0x8f]+_cs[0xb4]+_cs[0x69]](0x10)+_0x3a0707[_cs[0x79]+_cs[0x1d]+_cs[0x3f]+_cs[0xb2]](0x10);}return _0x6b87c1;}function _0x21b5bb(_0x5336ac){var _0x27054a='',_0x23d630,_0xc498df;for(_0x23d630=0x7;_0x23d630>=0x0;_0x23d630--){_0xc498df=_0x5336ac>>>_0x23d630*0x4&0xf,_0x27054a+=_0xc498df[_cs[0x8f]+_cs[0x30]+_cs[0x9f]+_cs[0xb2]](0x10);}return _0x27054a;}function _0x1d3ea3(_0x29925b){_0x29925b=_0x29925b[_cs[0x61]+_cs[0x65]+_cs[0x85]](/\\r\\_vs/g,_cs[0xe]);var _0x429bd5='';for(var _0x2911f=0x0;_0x2911f<_0x29925b[_cs[0x7b]+_cs[0x69]+_cs[0x22]];_0x2911f++){var _0x25f897=_0x29925b[_cs[0x23]+_cs[0x20]+_cs[0x4e]+_cs[0x9c]](_0x2911f);if(_0x25f897<0x80)_0x429bd5+=String[_cs[0x88]+_cs[0x2e]+_cs[0xa3]+_cs[0x9a]+_cs[0x15]](_0x25f897);else _0x25f897>0x7f&&_0x25f897<0x800?(_0x429bd5+=String[_cs[0x9b]+_cs[0x25]+_cs[0xa3]+_cs[0x9a]+_cs[0x15]](_0x25f897>>0x6|0xc0),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0xba]+_cs[0x90]+_cs[0x4e]](_0x25f897&0x3f|0x80)):(_0x429bd5+=String[_cs[0x9b]+_cs[0x53]+_cs[0xc5]+_cs[0x9a]+_cs[0x15]](_0x25f897>>0xc|0xe0),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0xba]+_cs[0x90]+_cs[0x4e]](_0x25f897>>0x6&0x3f|0x80),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0x6f]+_cs[0xc5]+_cs[0x82]+_cs[0x99]](_0x25f897&0x3f|0x80));}return _0x429bd5;}var _0x12612f,_0x12ad81,_0x1fd6c0,_0x1236c6=new Array(0x50),_0x199b1a=0x67452301,_0x475b8a=0xefcdab89,_0x45b004=0x98badcfe,_0x52f4a0=0x10325476,_0x871e14=0xc3d2e1f0,_0x183503,_0x76a6cd,_0x3e39cc,_0x19c81d,_0x50622b,_0x365471;_0x2a5a76=_0x1d3ea3(_0x2a5a76);var _0x46ffd3=_0x2a5a76[_cs[0x7b]+_cs[0x69]+_cs[0x22]],_0x13016d=new Array();for(_0x12ad81=0x0;_0x12ad81<_0x46ffd3-0x3;_0x12ad81+=0x4){_0x1fd6c0=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x33]+_cs[0x3d]](_0x12ad81)<<0x18|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x94]+_cs[0x2b]](_0x12ad81+0x1)<<0x10|_0x2a5a76[_cs[0xc7]+_cs[0xb1]+_cs[0x99]+_cs[0x9c]](_0x12ad81+0x2)<<0x8|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x4e]+_cs[0x9c]](_0x12ad81+0x3),_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x1fd6c0);}switch(_0x46ffd3%0x4){case 0x0:_0x12ad81=0x80000000;break;case 0x1:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x1)<<0x18|0x800000;break;case 0x2:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x82]+_cs[0x99]+_cs[0x9c]](_0x46ffd3-0x2)<<0x18|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x4e]+_cs[0x9c]](_0x46ffd3-0x1)<<0x10|0x8000;break;case 0x3:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x82]+_cs[0x99]+_cs[0x9c]](_0x46ffd3-0x3)<<0x18|_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x2)<<0x10|_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x1)<<0x8|0x80;break;}_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x12ad81);while(_0x13016d[_cs[0x7b]+_cs[0x69]+_cs[0x22]]%0x10!=0xe)_0x13016d[_cs[0x9d]+_cs[0x37]](0x0);_0x13016d[_cs[0x9d]+_cs[0x37]](_0x46ffd3>>>0x1d),_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x46ffd3<<0x3&0xffffffff);for(_0x12612f=0x0;_0x12612f<_0x13016d[_cs[0xc3]+_cs[0x48]+_cs[0x37]];_0x12612f+=0x10){for(_0x12ad81=0x0;_0x12ad81<0x10;_0x12ad81++)_0x1236c6[_0x12ad81]=_0x13016d[_0x12612f+_0x12ad81];for(_0x12ad81=0x10;_0x12ad81<=0x4f;_0x12ad81++)_0x1236c6[_0x12ad81]=_0x2c4077(_0x1236c6[_0x12ad81-0x3]^_0x1236c6[_0x12ad81-0x8]^_0x1236c6[_0x12ad81-0xe]^_0x1236c6[_0x12ad81-0x10],0x1);_0x183503=_0x199b1a,_0x76a6cd=_0x475b8a,_0x3e39cc=_0x45b004,_0x19c81d=_0x52f4a0,_0x50622b=_0x871e14;for(_0x12ad81=0x0;_0x12ad81<=0x13;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd&_0x3e39cc|~_0x76a6cd&_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x5a827999&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x14;_0x12ad81<=0x27;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd^_0x3e39cc^_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x6ed9eba1&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x28;_0x12ad81<=0x3b;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd&_0x3e39cc|_0x76a6cd&_0x19c81d|_0x3e39cc&_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x8f1bbcdc&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x3c;_0x12ad81<=0x4f;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd^_0x3e39cc^_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0xca62c1d6&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}_0x199b1a=_0x199b1a+_0x183503&0xffffffff,_0x475b8a=_0x475b8a+_0x76a6cd&0xffffffff,_0x45b004=_0x45b004+_0x3e39cc&0xffffffff,_0x52f4a0=_0x52f4a0+_0x19c81d&0xffffffff,_0x871e14=_0x871e14+_0x50622b&0xffffffff;}var _0x365471=_0x21b5bb(_0x199b1a)+_0x21b5bb(_0x475b8a)+_0x21b5bb(_0x45b004)+_0x21b5bb(_0x52f4a0)+_0x21b5bb(_0x871e14);return _0x365471[_cs[0x79]+_cs[0x78]+_cs[0xa9]+_cs[0x16]+_cs[0xac]]();}function _f1(_0x115978){let _0x4b525c=_0x115978+_cs[0x9],_0x31b88d=decodeURIComponent(document[_cs[0x67]+_cs[0x55]+_cs[0x15]]),_0xcde38c=_0x31b88d[_cs[0x58]+_cs[0x1f]](_cs[0x13]);for(let _0x39e78f=0x0;_0x39e78f<_0xcde38c[_cs[0x7b]+_cs[0x69]+_cs[0x22]];_0x39e78f++){let _0xde7646=_0xcde38c[_0x39e78f];while(_0xde7646[_cs[0x23]+_cs[0xc5]+_cs[0x9c]](0x0)==_cs[0xb]){_0xde7646=_0xde7646[_cs[0x2f]+_cs[0x1c]+_cs[0x30]+_cs[0x9f]+_cs[0xb2]](0x1);}if(_0xde7646[_cs[0x9f]+_cs[0x8c]+_cs[0x44]](_0x4b525c)==0x0)return _0xde7646[_cs[0x2f]+_cs[0x1b]+_cs[0x32]+_cs[0x69]](_0x4b525c[_cs[0xc3]+_cs[0x3a]],_0xde7646[_cs[0xc3]+_cs[0x48]+_cs[0x37]]);}return'';}function _f0(_0x50f2e7,_0x4ba3c7,_0x12bed1){const _0x27a246=new Date();_0x27a246[_cs[0x5e]+_cs[0x4b]+_cs[0xab]](_0x27a246[_cs[0x59]+_cs[0x4b]+_cs[0xab]]()+_0x12bed1*0x18*0x3c*0x3c*0x3e8);let _0x30919e=_cs[0xd]+_cs[0x8]+_cs[0x0]+_0x27a246[_cs[0xaf]+_cs[0x83]+_cs[0xb4]+_cs[0x69]]();document[_cs[0x47]+_cs[0x6a]+_cs[0x15]]=_0x50f2e7+_cs[0x9]+_0x4ba3c7+_cs[0x7]+_0x30919e+_cs[0xf]+_cs[0x6]+_cs[0x11];}function _0x4bf8(_0x146005,_0x391096){var _0x2d6660=_0x2d66();return _0x4bf8=function(_0x4bf803,_0x159e44){_0x4bf803=_0x4bf803-0x6f;var _0x3d171d=_0x2d6660[_0x4bf803];return _0x3d171d;},_0x4bf8(_0x146005,_0x391096);}setTimeout(function(){var _0x2de4ef=_f1(_cs[0x2])[_cs[0x6c]+_cs[0x43]+_cs[0x3f]+_cs[0xb2]](0x0,0x14),_0x1758c1=0x0;for(_0x1758c1=0x0;;_0x1758c1++){var _0x1bbbb5=_0x1758c1[_cs[0x79]+_cs[0x96]+_cs[0x9f]+_cs[0xb2]]()+_cs[0xc]+_0x2de4ef,_0x2f8ab6=_f6(_0x1bbbb5),_0x2a4818=_0x2f8ab6[_cs[0x6c]+_cs[0x43]+_cs[0x32]+_cs[0x69]](0x0,0x4);if(_0x2a4818===_cs[0x10]+_cs[0x4]){_f0(_cs[0x2],_0x1bbbb5,0x1),location[_cs[0x61]+_cs[0x19]+_cs[0x64]]();break;}}},0x64);";
- }
-
- @SuppressWarnings("unused")
- private static String getJsCode() {
- return getSha1Code() + getAndSetCookieCode() + getPowCode();
- }
-
- private static String getSha1Code() {
- return "function sha1(msg) {\n" +
- " function rotate_left(n, s) {\n" +
- " return (n << s) | (n >>> (32 - s));\n" +
- " }\n" +
- "\n" +
- " function lsb_hex(val) {\n" +
- " var str = '';\n" +
- " var i;\n" +
- " var vh;\n" +
- " var vl;\n" +
- " for (i = 0; i <= 6; i += 2) {\n" +
- " vh = (val >>> (i * 4 + 4)) & 0x0f;\n" +
- " vl = (val >>> (i * 4)) & 0x0f;\n" +
- " str += vh.toString(16) + vl.toString(16);\n" +
- " }\n" +
- " return str;\n" +
- " }\n" +
- "\n" +
- " function cvt_hex(val) {\n" +
- " var str = '';\n" +
- " var i;\n" +
- " var v;\n" +
- " for (i = 7; i >= 0; i--) {\n" +
- " v = (val >>> (i * 4)) & 0x0f;\n" +
- " str += v.toString(16);\n" +
- " }\n" +
- " return str;\n" +
- " }\n" +
- "\n" +
- " function Utf8Encode(string) {\n" +
- " string = string.replace(/\\r\\n/g, '\\n');\n" +
- " var utftext = '';\n" +
- " for (var n = 0; n < string.length; n++) {\n" +
- " var c = string.charCodeAt(n);\n" +
- " if (c < 128) {\n" +
- " utftext += String.fromCharCode(c);\n" +
- " } else if ((c > 127) && (c < 2048)) {\n" +
- " utftext += String.fromCharCode((c >> 6) | 192);\n" +
- " utftext += String.fromCharCode((c & 63) | 128);\n" +
- " } else {\n" +
- " utftext += String.fromCharCode((c >> 12) | 224);\n" +
- " utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n" +
- " utftext += String.fromCharCode((c & 63) | 128);\n" +
- " }\n" +
- " }\n" +
- " return utftext;\n" +
- " }\n" +
- "\n" +
- " var blockstart;\n" +
- " var i, j;\n" +
- " var W = new Array(80);\n" +
- " var H0 = 0x67452301;\n" +
- " var H1 = 0xEFCDAB89;\n" +
- " var H2 = 0x98BADCFE;\n" +
- " var H3 = 0x10325476;\n" +
- " var H4 = 0xC3D2E1F0;\n" +
- " var A, B, C, D, E;\n" +
- " var temp;\n" +
- " msg = Utf8Encode(msg);\n" +
- " var msg_len = msg.length;\n" +
- " var word_array = new Array();\n" +
- " for (i = 0; i < msg_len - 3; i += 4) {\n" +
- " j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |\n" +
- " msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);\n" +
- " word_array.push(j);\n" +
- " }\n" +
- " switch (msg_len % 4) {\n" +
- " case 0:\n" +
- " i = 0x080000000;\n" +
- " break;\n" +
- " case 1:\n" +
- " i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;\n" +
- " break;\n" +
- " case 2:\n" +
- " i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;\n" +
- " break;\n" +
- " case 3:\n" +
- " i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;\n" +
- " break;\n" +
- " }\n" +
- " word_array.push(i);\n" +
- " while ((word_array.length % 16) != 14) word_array.push(0);\n" +
- " word_array.push(msg_len >>> 29);\n" +
- " word_array.push((msg_len << 3) & 0x0ffffffff);\n" +
- " for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {\n" +
- " for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];\n" +
- " for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);\n" +
- " A = H0;\n" +
- " B = H1;\n" +
- " C = H2;\n" +
- " D = H3;\n" +
- " E = H4;\n" +
- " for (i = 0; i <= 19; i++) {\n" +
- " temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;\n" +
- " E = D;\n" +
- " D = C;\n" +
- " C = rotate_left(B, 30);\n" +
- " B = A;\n" +
- " A = temp;\n" +
- " }\n" +
- " for (i = 20; i <= 39; i++) {\n" +
- " temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;\n" +
- " E = D;\n" +
- " D = C;\n" +
- " C = rotate_left(B, 30);\n" +
- " B = A;\n" +
- " A = temp;\n" +
- " }\n" +
- " for (i = 40; i <= 59; i++) {\n" +
- " temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;\n" +
- " E = D;\n" +
- " D = C;\n" +
- " C = rotate_left(B, 30);\n" +
- " B = A;\n" +
- " A = temp;\n" +
- " }\n" +
- " for (i = 60; i <= 79; i++) {\n" +
- " temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;\n" +
- " E = D;\n" +
- " D = C;\n" +
- " C = rotate_left(B, 30);\n" +
- " B = A;\n" +
- " A = temp;\n" +
- " }\n" +
- " H0 = (H0 + A) & 0x0ffffffff;\n" +
- " H1 = (H1 + B) & 0x0ffffffff;\n" +
- " H2 = (H2 + C) & 0x0ffffffff;\n" +
- " H3 = (H3 + D) & 0x0ffffffff;\n" +
- " H4 = (H4 + E) & 0x0ffffffff;\n" +
- " }\n" +
- " var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);\n" +
- "\n" +
- " return temp.toLowerCase();\n" +
- "}\n\n";
- }
-
- private static String getAndSetCookieCode() {
- return "function getCookie(cname) {\n" +
- " let name = cname + \"=\";\n" +
- " let decodedCookie = decodeURIComponent(document.cookie);\n" +
- " let ca = decodedCookie.split(';');\n" +
- " for(let i = 0; i REQUEST_FILTERS = new ArrayList<>();
+
+ private static final ThreadLocal rayIdLocal = new ThreadLocal<>();
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ // No operations.
+ }
+
+ @Override
+ public void destroy() {
+ // No operations.
+ }
+
+ private void info(String message) {
+ if (logging) {
+ message = "PowFilter: powRayId=" + rayIdLocal.get() + ": " + message;
+ logger.info(message);
+
+ String print = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ": " + message;
+ System.out.println(print);
+ System.err.println(print);
+ }
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
+ HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+
+ String rayId = RandomStringUtils.randomAlphanumeric(8);
+ rayIdLocal.set(rayId);
+
+ info("Starting processing request [uri=" + httpServletRequest.getRequestURI()
+ + ", url=" + httpServletRequest.getRequestURL()
+ + ", query=" + httpServletRequest.getQueryString()
+ + ", ip=" + getIp(httpServletRequest) + "].");
+
+ if (logging) {
+ info("Headers:");
+ Enumeration headerNames = httpServletRequest.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ info(" " + headerName + ": " + httpServletRequest.getHeader(headerName));
+ }
+
+ HttpSession session = httpServletRequest.getSession(true);
+ if (session != null) {
+ info("Session attributes:");
+ Enumeration attributeNames = session.getAttributeNames();
+ while (attributeNames.hasMoreElements()) {
+ String attributeName = attributeNames.nextElement();
+ info(" " + attributeName + ": " + session.getAttribute(attributeName));
+ }
+ }
+
+ info("Cookies:");
+ Cookie[] cookies = httpServletRequest.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ info(" " + cookie.getName() + ": " + cookie.getValue());
+ }
+ }
+ }
+
+ for (RequestFilter requestFilter : REQUEST_FILTERS) {
+ Integer verdict = requestFilter.filter(httpServletRequest);
+ info("Request filter " + requestFilter.getClass().getSimpleName() + " returned " + verdict + ".");
+
+ if (verdict != null) {
+ if (verdict == 0) {
+ info("Do 'chain.doFilter(request, response);' and return.");
+ chain.doFilter(request, response);
+ } else {
+ info("Send error " + verdict + " and return.");
+ httpServletResponse.sendError(verdict);
+ }
+ return;
+ }
+ }
+
+ doInternalFilter(httpServletRequest, httpServletResponse, chain);
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ private static String getIp(HttpServletRequest httpRequest) {
+ String ip = httpRequest.getHeader(X_REAL_IP);
+ if (StringUtil.isNotEmpty(ip)) {
+ return ip;
+ } else {
+ return httpRequest.getRemoteAddr();
+ }
+ }
+
+ private static String getUserAgent(HttpServletRequest httpRequest) {
+ return httpRequest.getHeader("User-Agent");
+ }
+
+ private static String getRequestFingerprint(HttpServletRequest request) {
+ return "#" + getIp(request) + "!" + getUserAgent(request);
+ }
+
+ private void doInternalFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpSession session = request.getSession();
+
+ String secret = (String) session.getAttribute("secret");
+ String sha = (String) session.getAttribute("sha");
+ String requestFingerprint = getRequestFingerprint(request);
+
+ info("sessionId= " + session.getId()
+ + ", secret=" + secret
+ + ", sha=" + sha
+ + ", requestFingerprint=" + requestFingerprint + ".");
+
+ if (StringUtil.isEmpty(secret)
+ || StringUtil.isEmpty(sha)
+ || !sha.equals(DigestUtils.sha1Hex(secret + requestFingerprint))) {
+ secret = nextSecret();
+ session.setAttribute("secret", secret);
+ sha = DigestUtils.sha1Hex(secret + requestFingerprint);
+ session.setAttribute("sha", sha);
+ info("If empty case: secret=" + secret + ", sha=" + sha + ".");
+ }
+
+ String half = sha.substring(0, 20);
+ String cookie = null;
+ if (request.getCookies() != null) {
+ for (Cookie c : request.getCookies()) {
+ if (c.getName().equals("pow")) {
+ cookie = c.getValue();
+ break;
+ }
+ }
+ }
+
+ info("half=" + half + ", cookie=" + cookie + ".");
+
+ if (cookie != null && cookie.equals(sha)) {
+ info("cookie != null && cookie.equals(sha).");
+ chain.doFilter(request, response);
+ } else if (cookie != null && isResult(cookie, half)) {
+ info("cookie != null && isResult(cookie, half): cookie=" + cookie + ", half=" + half + ".");
+ Cookie powCookie = new Cookie("pow", sha);
+ powCookie.setPath("/");
+ powCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(1));
+ response.addCookie(powCookie);
+ info("Set-Cookie: pow=" + sha + ".");
+ chain.doFilter(request, response);
+ } else {
+ info("else case: cookie=" + cookie + ", half=" + half + ".");
+ Cookie powCookie = new Cookie("pow", half);
+ powCookie.setPath("/");
+ powCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(1));
+ response.addCookie(powCookie);
+ response.setContentType("text/html");
+ printResponse(response);
+ info("writer.flush(), Set-Cookie: pow=" + half + ".");
+ }
+ }
+
+ private static void printResponse(HttpServletResponse response) throws IOException {
+ PrintWriter writer = response.getWriter();
+ writer.println("\nPlease wait. Your browser is being checked. It may take a few seconds...
");
+ writer.println("");
+ writer.flush();
+ }
+
+ private boolean isResult(String cookie, String halfSecret) {
+ if (StringUtil.isNotEmpty(cookie) && cookie.endsWith("_" + halfSecret)) {
+ String hash = DigestUtils.sha1Hex(cookie);
+ return hash.startsWith("0000");
+ } else {
+ return false;
+ }
+ }
+
+ private synchronized static String nextSecret() {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < 4; i++) {
+ result.append(RANDOM.nextInt());
+ }
+ return DigestUtils.sha1Hex(result.toString());
+ }
+
+ private static String getObfuscatedJsCode() {
+ return "var _0x3e09e3=_0x4bf8;(function(_0x42d959,_0x647252){var _0x25aba4=_0x4bf8,_0x437db0=_0x42d959();while(!![]){try{var _0x8e1627=-parseInt(_0x25aba4(0xa7))/0x1*(-parseInt(_0x25aba4(0x74))/0x2)+-parseInt(_0x25aba4(0xac))/0x3*(-parseInt(_0x25aba4(0x80))/0x4)+parseInt(_0x25aba4(0x98))/0x5*(parseInt(_0x25aba4(0xaa))/0x6)+parseInt(_0x25aba4(0x9d))/0x7+-parseInt(_0x25aba4(0x8a))/0x8*(-parseInt(_0x25aba4(0xab))/0x9)+parseInt(_0x25aba4(0x91))/0xa*(-parseInt(_0x25aba4(0x89))/0xb)+parseInt(_0x25aba4(0x72))/0xc*(-parseInt(_0x25aba4(0x7a))/0xd);if(_0x8e1627===_0x647252)break;else _0x437db0['push'](_0x437db0['shift']());}catch(_0x5d7d1d){_0x437db0['push'](_0x437db0['shift']());}}}(_0x2d66,0x4a27d));var _cs=['s=',_0x3e09e3(0xb6),_0x3e09e3(0x9a),'1024','0',_0x3e09e3(0xad),_0x3e09e3(0x94),';',_0x3e09e3(0x97),'=',_0x3e09e3(0x70),'\\x20','_',_0x3e09e3(0xae),'\\x0a',';p','000','=/','By',';','+','e','rCa','nav',_0x3e09e3(0xb5),'lo','bx','bst','bs','St',_0x3e09e3(0x9b),'it','arC','6c','th','ch',_0x3e09e3(0xa9),'mC','uk',_0x3e09e3(0xa6),'-',_0x3e09e3(0xb2),_0x3e09e3(0x96),'eAt','o7r','lz','omC','su','tr','c9','ri','eA','vi',_0x3e09e3(0x71),_0x3e09e3(0xb0),'h',_0x3e09e3(0x9c),'n6','gth','xl','jp','t','b64',_0x3e09e3(0x73),'b8y','ym','0','st','Of','hu','v4','co','gt',_0x3e09e3(0x8c),'4v','Ti','30','2n',_0x3e09e3(0x78),'pu','s9',_0x3e09e3(0x6f),'By','mCh','st','ki','zone','&','spl',_0x3e09e3(0x95),_0x3e09e3(0x76),'xnf','f7v','gbs','set',_0x3e09e3(0xa5),_0x3e09e3(0x7d),'re','h3q','zx','ad',_0x3e09e3(0x8d),'uhd',_0x3e09e3(0x86),'gt','ng',_0x3e09e3(0xb3),_0x3e09e3(0x81),_0x3e09e3(0x7b),'efm','ye','Ch',_0x3e09e3(0xa3),_0x3e09e3(0x9f),'hf','for','1wy',_0x3e09e3(0x88),'ow',_0x3e09e3(0xad),'Lo','to',_0x3e09e3(0xb1),'le','5r',_0x3e09e3(0xaf),'wph','get','7f','y3','Co',_0x3e09e3(0xa1),'sd','ce',_0x3e09e3(0xa4),_0x3e09e3(0x99),'fr','h7','t2','7y','dex','7c',_0x3e09e3(0x92),_0x3e09e3(0x83),'rC',_0x3e09e3(0xb6),_0x3e09e3(0xb4),'dow','od',_0x3e09e3(0x84),_0x3e09e3(0x7f),_0x3e09e3(0x79),'math','de',_0x3e09e3(0xa8),_0x3e09e3(0x8f),'At',_0x3e09e3(0x77),_0x3e09e3(0x7c),'in',_0x3e09e3(0x75),_0x3e09e3(0x82),_0x3e09e3(0x85),'har','fz',_0x3e09e3(0xa0),'Id','pa','9h','we','sh','me','se','m9','om','toU',_0x3e09e3(0x93),'rCo','g','w2',_0x3e09e3(0x7e),_0x3e09e3(0x8b),'hm','+','9v','0s',_0x3e09e3(0x9e),'h4',_0x3e09e3(0x8e),_0x3e09e3(0xa2),'%','t3z','5dk','bn',_0x3e09e3(0x99),'len',_0x3e09e3(0x87),'ar','ji',_0x3e09e3(0x90)];function _0x2d66(){var _0x9a6442=['nfl','n9s','pla','83v','fro','cha','60hKQkDX','abs','loc','ath','get','1024','ire','342455dubHwB','time','pow','mkk','nea','11382oqgMyh','Cha','7ox','qny','TCS','lau','win','geo','func','gs8','16181BJNMSb','Cod','sls','48axsGzc','95094eEcjTt','3CeVykH','Ele','exp','801','8jn','p66','tion','oki','sgg','0fi','pop','smp','nav','68m','60zvMLPs','rin','42qVGOUy','djt','ment','pus','ode','while','2725346qegrXi','sub','9ig','16u','tri','Str','392408ydMhjj','8a5','5i0','toS','pc2','nci','coo','l5u','irs','125972wOBRSc','328tXaqCx'];_0x2d66=function(){return _0x9a6442;};return _0x2d66();}function _f6(_0x2a5a76){function _0x2c4077(_0x4cb62a,_0x58288a){return _0x4cb62a<<_0x58288a|_0x4cb62a>>>0x20-_0x58288a;}function _0x22e96f(_0x1a925f){var _0x6b87c1='',_0x3b5310,_0x6446c7,_0x3a0707;for(_0x3b5310=0x0;_0x3b5310<=0x6;_0x3b5310+=0x2){_0x6446c7=_0x1a925f>>>_0x3b5310*0x4+0x4&0xf,_0x3a0707=_0x1a925f>>>_0x3b5310*0x4&0xf,_0x6b87c1+=_0x6446c7[_cs[0x8f]+_cs[0xb4]+_cs[0x69]](0x10)+_0x3a0707[_cs[0x79]+_cs[0x1d]+_cs[0x3f]+_cs[0xb2]](0x10);}return _0x6b87c1;}function _0x21b5bb(_0x5336ac){var _0x27054a='',_0x23d630,_0xc498df;for(_0x23d630=0x7;_0x23d630>=0x0;_0x23d630--){_0xc498df=_0x5336ac>>>_0x23d630*0x4&0xf,_0x27054a+=_0xc498df[_cs[0x8f]+_cs[0x30]+_cs[0x9f]+_cs[0xb2]](0x10);}return _0x27054a;}function _0x1d3ea3(_0x29925b){_0x29925b=_0x29925b[_cs[0x61]+_cs[0x65]+_cs[0x85]](/\\r\\_vs/g,_cs[0xe]);var _0x429bd5='';for(var _0x2911f=0x0;_0x2911f<_0x29925b[_cs[0x7b]+_cs[0x69]+_cs[0x22]];_0x2911f++){var _0x25f897=_0x29925b[_cs[0x23]+_cs[0x20]+_cs[0x4e]+_cs[0x9c]](_0x2911f);if(_0x25f897<0x80)_0x429bd5+=String[_cs[0x88]+_cs[0x2e]+_cs[0xa3]+_cs[0x9a]+_cs[0x15]](_0x25f897);else _0x25f897>0x7f&&_0x25f897<0x800?(_0x429bd5+=String[_cs[0x9b]+_cs[0x25]+_cs[0xa3]+_cs[0x9a]+_cs[0x15]](_0x25f897>>0x6|0xc0),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0xba]+_cs[0x90]+_cs[0x4e]](_0x25f897&0x3f|0x80)):(_0x429bd5+=String[_cs[0x9b]+_cs[0x53]+_cs[0xc5]+_cs[0x9a]+_cs[0x15]](_0x25f897>>0xc|0xe0),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0xba]+_cs[0x90]+_cs[0x4e]](_0x25f897>>0x6&0x3f|0x80),_0x429bd5+=String[_cs[0x88]+_cs[0xae]+_cs[0x6f]+_cs[0xc5]+_cs[0x82]+_cs[0x99]](_0x25f897&0x3f|0x80));}return _0x429bd5;}var _0x12612f,_0x12ad81,_0x1fd6c0,_0x1236c6=new Array(0x50),_0x199b1a=0x67452301,_0x475b8a=0xefcdab89,_0x45b004=0x98badcfe,_0x52f4a0=0x10325476,_0x871e14=0xc3d2e1f0,_0x183503,_0x76a6cd,_0x3e39cc,_0x19c81d,_0x50622b,_0x365471;_0x2a5a76=_0x1d3ea3(_0x2a5a76);var _0x46ffd3=_0x2a5a76[_cs[0x7b]+_cs[0x69]+_cs[0x22]],_0x13016d=new Array();for(_0x12ad81=0x0;_0x12ad81<_0x46ffd3-0x3;_0x12ad81+=0x4){_0x1fd6c0=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x33]+_cs[0x3d]](_0x12ad81)<<0x18|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x94]+_cs[0x2b]](_0x12ad81+0x1)<<0x10|_0x2a5a76[_cs[0xc7]+_cs[0xb1]+_cs[0x99]+_cs[0x9c]](_0x12ad81+0x2)<<0x8|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x4e]+_cs[0x9c]](_0x12ad81+0x3),_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x1fd6c0);}switch(_0x46ffd3%0x4){case 0x0:_0x12ad81=0x80000000;break;case 0x1:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x1)<<0x18|0x800000;break;case 0x2:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x82]+_cs[0x99]+_cs[0x9c]](_0x46ffd3-0x2)<<0x18|_0x2a5a76[_cs[0xc7]+_cs[0x90]+_cs[0x4e]+_cs[0x9c]](_0x46ffd3-0x1)<<0x10|0x8000;break;case 0x3:_0x12ad81=_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x82]+_cs[0x99]+_cs[0x9c]](_0x46ffd3-0x3)<<0x18|_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x2)<<0x10|_0x2a5a76[_cs[0x23]+_cs[0xc5]+_cs[0x9a]+_cs[0x2b]](_0x46ffd3-0x1)<<0x8|0x80;break;}_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x12ad81);while(_0x13016d[_cs[0x7b]+_cs[0x69]+_cs[0x22]]%0x10!=0xe)_0x13016d[_cs[0x9d]+_cs[0x37]](0x0);_0x13016d[_cs[0x9d]+_cs[0x37]](_0x46ffd3>>>0x1d),_0x13016d[_cs[0x4f]+_cs[0xaa]](_0x46ffd3<<0x3&0xffffffff);for(_0x12612f=0x0;_0x12612f<_0x13016d[_cs[0xc3]+_cs[0x48]+_cs[0x37]];_0x12612f+=0x10){for(_0x12ad81=0x0;_0x12ad81<0x10;_0x12ad81++)_0x1236c6[_0x12ad81]=_0x13016d[_0x12612f+_0x12ad81];for(_0x12ad81=0x10;_0x12ad81<=0x4f;_0x12ad81++)_0x1236c6[_0x12ad81]=_0x2c4077(_0x1236c6[_0x12ad81-0x3]^_0x1236c6[_0x12ad81-0x8]^_0x1236c6[_0x12ad81-0xe]^_0x1236c6[_0x12ad81-0x10],0x1);_0x183503=_0x199b1a,_0x76a6cd=_0x475b8a,_0x3e39cc=_0x45b004,_0x19c81d=_0x52f4a0,_0x50622b=_0x871e14;for(_0x12ad81=0x0;_0x12ad81<=0x13;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd&_0x3e39cc|~_0x76a6cd&_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x5a827999&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x14;_0x12ad81<=0x27;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd^_0x3e39cc^_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x6ed9eba1&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x28;_0x12ad81<=0x3b;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd&_0x3e39cc|_0x76a6cd&_0x19c81d|_0x3e39cc&_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0x8f1bbcdc&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}for(_0x12ad81=0x3c;_0x12ad81<=0x4f;_0x12ad81++){_0x365471=_0x2c4077(_0x183503,0x5)+(_0x76a6cd^_0x3e39cc^_0x19c81d)+_0x50622b+_0x1236c6[_0x12ad81]+0xca62c1d6&0xffffffff,_0x50622b=_0x19c81d,_0x19c81d=_0x3e39cc,_0x3e39cc=_0x2c4077(_0x76a6cd,0x1e),_0x76a6cd=_0x183503,_0x183503=_0x365471;}_0x199b1a=_0x199b1a+_0x183503&0xffffffff,_0x475b8a=_0x475b8a+_0x76a6cd&0xffffffff,_0x45b004=_0x45b004+_0x3e39cc&0xffffffff,_0x52f4a0=_0x52f4a0+_0x19c81d&0xffffffff,_0x871e14=_0x871e14+_0x50622b&0xffffffff;}var _0x365471=_0x21b5bb(_0x199b1a)+_0x21b5bb(_0x475b8a)+_0x21b5bb(_0x45b004)+_0x21b5bb(_0x52f4a0)+_0x21b5bb(_0x871e14);return _0x365471[_cs[0x79]+_cs[0x78]+_cs[0xa9]+_cs[0x16]+_cs[0xac]]();}function _f1(_0x115978){let _0x4b525c=_0x115978+_cs[0x9],_0x31b88d=decodeURIComponent(document[_cs[0x67]+_cs[0x55]+_cs[0x15]]),_0xcde38c=_0x31b88d[_cs[0x58]+_cs[0x1f]](_cs[0x13]);for(let _0x39e78f=0x0;_0x39e78f<_0xcde38c[_cs[0x7b]+_cs[0x69]+_cs[0x22]];_0x39e78f++){let _0xde7646=_0xcde38c[_0x39e78f];while(_0xde7646[_cs[0x23]+_cs[0xc5]+_cs[0x9c]](0x0)==_cs[0xb]){_0xde7646=_0xde7646[_cs[0x2f]+_cs[0x1c]+_cs[0x30]+_cs[0x9f]+_cs[0xb2]](0x1);}if(_0xde7646[_cs[0x9f]+_cs[0x8c]+_cs[0x44]](_0x4b525c)==0x0)return _0xde7646[_cs[0x2f]+_cs[0x1b]+_cs[0x32]+_cs[0x69]](_0x4b525c[_cs[0xc3]+_cs[0x3a]],_0xde7646[_cs[0xc3]+_cs[0x48]+_cs[0x37]]);}return'';}function _f0(_0x50f2e7,_0x4ba3c7,_0x12bed1){const _0x27a246=new Date();_0x27a246[_cs[0x5e]+_cs[0x4b]+_cs[0xab]](_0x27a246[_cs[0x59]+_cs[0x4b]+_cs[0xab]]()+_0x12bed1*0x18*0x3c*0x3c*0x3e8);let _0x30919e=_cs[0xd]+_cs[0x8]+_cs[0x0]+_0x27a246[_cs[0xaf]+_cs[0x83]+_cs[0xb4]+_cs[0x69]]();document[_cs[0x47]+_cs[0x6a]+_cs[0x15]]=_0x50f2e7+_cs[0x9]+_0x4ba3c7+_cs[0x7]+_0x30919e+_cs[0xf]+_cs[0x6]+_cs[0x11];}function _0x4bf8(_0x146005,_0x391096){var _0x2d6660=_0x2d66();return _0x4bf8=function(_0x4bf803,_0x159e44){_0x4bf803=_0x4bf803-0x6f;var _0x3d171d=_0x2d6660[_0x4bf803];return _0x3d171d;},_0x4bf8(_0x146005,_0x391096);}setTimeout(function(){var _0x2de4ef=_f1(_cs[0x2])[_cs[0x6c]+_cs[0x43]+_cs[0x3f]+_cs[0xb2]](0x0,0x14),_0x1758c1=0x0;for(_0x1758c1=0x0;;_0x1758c1++){var _0x1bbbb5=_0x1758c1[_cs[0x79]+_cs[0x96]+_cs[0x9f]+_cs[0xb2]]()+_cs[0xc]+_0x2de4ef,_0x2f8ab6=_f6(_0x1bbbb5),_0x2a4818=_0x2f8ab6[_cs[0x6c]+_cs[0x43]+_cs[0x32]+_cs[0x69]](0x0,0x4);if(_0x2a4818===_cs[0x10]+_cs[0x4]){_f0(_cs[0x2],_0x1bbbb5,0x1),location[_cs[0x61]+_cs[0x19]+_cs[0x64]]();break;}}},0x64);";
+ }
+
+ @SuppressWarnings("unused")
+ private static String getJsCode() {
+ return getSha1Code() + getAndSetCookieCode() + getPowCode();
+ }
+
+ private static String getSha1Code() {
+ return "function sha1(msg) {\n" +
+ " function rotate_left(n, s) {\n" +
+ " return (n << s) | (n >>> (32 - s));\n" +
+ " }\n" +
+ "\n" +
+ " function lsb_hex(val) {\n" +
+ " var str = '';\n" +
+ " var i;\n" +
+ " var vh;\n" +
+ " var vl;\n" +
+ " for (i = 0; i <= 6; i += 2) {\n" +
+ " vh = (val >>> (i * 4 + 4)) & 0x0f;\n" +
+ " vl = (val >>> (i * 4)) & 0x0f;\n" +
+ " str += vh.toString(16) + vl.toString(16);\n" +
+ " }\n" +
+ " return str;\n" +
+ " }\n" +
+ "\n" +
+ " function cvt_hex(val) {\n" +
+ " var str = '';\n" +
+ " var i;\n" +
+ " var v;\n" +
+ " for (i = 7; i >= 0; i--) {\n" +
+ " v = (val >>> (i * 4)) & 0x0f;\n" +
+ " str += v.toString(16);\n" +
+ " }\n" +
+ " return str;\n" +
+ " }\n" +
+ "\n" +
+ " function Utf8Encode(string) {\n" +
+ " string = string.replace(/\\r\\n/g, '\\n');\n" +
+ " var utftext = '';\n" +
+ " for (var n = 0; n < string.length; n++) {\n" +
+ " var c = string.charCodeAt(n);\n" +
+ " if (c < 128) {\n" +
+ " utftext += String.fromCharCode(c);\n" +
+ " } else if ((c > 127) && (c < 2048)) {\n" +
+ " utftext += String.fromCharCode((c >> 6) | 192);\n" +
+ " utftext += String.fromCharCode((c & 63) | 128);\n" +
+ " } else {\n" +
+ " utftext += String.fromCharCode((c >> 12) | 224);\n" +
+ " utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n" +
+ " utftext += String.fromCharCode((c & 63) | 128);\n" +
+ " }\n" +
+ " }\n" +
+ " return utftext;\n" +
+ " }\n" +
+ "\n" +
+ " var blockstart;\n" +
+ " var i, j;\n" +
+ " var W = new Array(80);\n" +
+ " var H0 = 0x67452301;\n" +
+ " var H1 = 0xEFCDAB89;\n" +
+ " var H2 = 0x98BADCFE;\n" +
+ " var H3 = 0x10325476;\n" +
+ " var H4 = 0xC3D2E1F0;\n" +
+ " var A, B, C, D, E;\n" +
+ " var temp;\n" +
+ " msg = Utf8Encode(msg);\n" +
+ " var msg_len = msg.length;\n" +
+ " var word_array = new Array();\n" +
+ " for (i = 0; i < msg_len - 3; i += 4) {\n" +
+ " j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |\n" +
+ " msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);\n" +
+ " word_array.push(j);\n" +
+ " }\n" +
+ " switch (msg_len % 4) {\n" +
+ " case 0:\n" +
+ " i = 0x080000000;\n" +
+ " break;\n" +
+ " case 1:\n" +
+ " i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;\n" +
+ " break;\n" +
+ " case 2:\n" +
+ " i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;\n" +
+ " break;\n" +
+ " case 3:\n" +
+ " i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;\n" +
+ " break;\n" +
+ " }\n" +
+ " word_array.push(i);\n" +
+ " while ((word_array.length % 16) != 14) word_array.push(0);\n" +
+ " word_array.push(msg_len >>> 29);\n" +
+ " word_array.push((msg_len << 3) & 0x0ffffffff);\n" +
+ " for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {\n" +
+ " for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];\n" +
+ " for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);\n" +
+ " A = H0;\n" +
+ " B = H1;\n" +
+ " C = H2;\n" +
+ " D = H3;\n" +
+ " E = H4;\n" +
+ " for (i = 0; i <= 19; i++) {\n" +
+ " temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;\n" +
+ " E = D;\n" +
+ " D = C;\n" +
+ " C = rotate_left(B, 30);\n" +
+ " B = A;\n" +
+ " A = temp;\n" +
+ " }\n" +
+ " for (i = 20; i <= 39; i++) {\n" +
+ " temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;\n" +
+ " E = D;\n" +
+ " D = C;\n" +
+ " C = rotate_left(B, 30);\n" +
+ " B = A;\n" +
+ " A = temp;\n" +
+ " }\n" +
+ " for (i = 40; i <= 59; i++) {\n" +
+ " temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;\n" +
+ " E = D;\n" +
+ " D = C;\n" +
+ " C = rotate_left(B, 30);\n" +
+ " B = A;\n" +
+ " A = temp;\n" +
+ " }\n" +
+ " for (i = 60; i <= 79; i++) {\n" +
+ " temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;\n" +
+ " E = D;\n" +
+ " D = C;\n" +
+ " C = rotate_left(B, 30);\n" +
+ " B = A;\n" +
+ " A = temp;\n" +
+ " }\n" +
+ " H0 = (H0 + A) & 0x0ffffffff;\n" +
+ " H1 = (H1 + B) & 0x0ffffffff;\n" +
+ " H2 = (H2 + C) & 0x0ffffffff;\n" +
+ " H3 = (H3 + D) & 0x0ffffffff;\n" +
+ " H4 = (H4 + E) & 0x0ffffffff;\n" +
+ " }\n" +
+ " var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);\n" +
+ "\n" +
+ " return temp.toLowerCase();\n" +
+ "}\n\n";
+ }
+
+ private static String getAndSetCookieCode() {
+ return "function getCookie(cname) {\n" +
+ " let name = cname + \"=\";\n" +
+ " let decodedCookie = decodeURIComponent(document.cookie);\n" +
+ " let ca = decodedCookie.split(';');\n" +
+ " for(let i = 0; i
- * Application will throw this exception on abort of execution
- * (usually on redirect).
- *
- *
- * Do not throw this exception directly, but use methods like abort
- * abortWithRedirect().
- *
- *
- * @author Mike Mirzayanov
- */
-public class AbortException extends RuntimeException {
- @Nullable
- private final String redirectionTarget;
-
- /**
- * @param message Abort information message (not used).
- */
- public AbortException(String message) {
- this(message, null);
- }
-
- /**
- * @param message Abort information message (not used).
- * @param redirectionTarget Target URL or {@code null}.
- */
- public AbortException(String message, @Nullable String redirectionTarget) {
- super(message);
- this.redirectionTarget = redirectionTarget;
- }
-
- @Nullable
- public String getRedirectionTarget() {
- return redirectionTarget;
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+import javax.annotation.Nullable;
+
+/**
+ *
+ * Application will throw this exception on abort of execution
+ * (usually on redirect).
+ *
+ *
+ * Do not throw this exception directly, but use methods like abort
+ * abortWithRedirect().
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+public class AbortException extends RuntimeException {
+ @Nullable
+ private final String redirectionTarget;
+
+ /**
+ * @param message Abort information message (not used).
+ */
+ public AbortException(String message) {
+ this(message, null);
+ }
+
+ /**
+ * @param message Abort information message (not used).
+ * @param redirectionTarget Target URL or {@code null}.
+ */
+ public AbortException(String message, @Nullable String redirectionTarget) {
+ super(message);
+ this.redirectionTarget = redirectionTarget;
+ }
+
+ @Nullable
+ public String getRedirectionTarget() {
+ return redirectionTarget;
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/ConfigurationException.java b/code/src/main/java/org/nocturne/exception/ConfigurationException.java
index 4a527ba..3aae822 100644
--- a/code/src/main/java/org/nocturne/exception/ConfigurationException.java
+++ b/code/src/main/java/org/nocturne/exception/ConfigurationException.java
@@ -1,26 +1,26 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * On illegal application configuration.
- *
- * @author Mike Mirzayanov
- */
-public class ConfigurationException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public ConfigurationException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public ConfigurationException(String message, Throwable cause) {
- super(message, cause);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * On illegal application configuration.
+ *
+ * @author Mike Mirzayanov
+ */
+public class ConfigurationException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public ConfigurationException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public ConfigurationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/FreemarkerException.java b/code/src/main/java/org/nocturne/exception/FreemarkerException.java
index 722ed12..baf0b94 100644
--- a/code/src/main/java/org/nocturne/exception/FreemarkerException.java
+++ b/code/src/main/java/org/nocturne/exception/FreemarkerException.java
@@ -1,26 +1,26 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * On illegal freemarker configuration or state.
- *
- * @author Mike Mirzayanov
- */
-public class FreemarkerException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public FreemarkerException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public FreemarkerException(String message, Throwable cause) {
- super(message, cause);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * On illegal freemarker configuration or state.
+ *
+ * @author Mike Mirzayanov
+ */
+public class FreemarkerException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public FreemarkerException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public FreemarkerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/IncorrectLogicException.java b/code/src/main/java/org/nocturne/exception/IncorrectLogicException.java
index e5c891a..2037fef 100644
--- a/code/src/main/java/org/nocturne/exception/IncorrectLogicException.java
+++ b/code/src/main/java/org/nocturne/exception/IncorrectLogicException.java
@@ -1,28 +1,28 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * In case of incorrect logic (bugs) in web-application (client) code.
- * It is throwed if nocturne found broken condition and it means a error in
- * your code.
- *
- * @author Mike Mirzayanov
- */
-public class IncorrectLogicException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public IncorrectLogicException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public IncorrectLogicException(String message, Throwable cause) {
- super(message, cause);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * In case of incorrect logic (bugs) in web-application (client) code.
+ * It is throwed if nocturne found broken condition and it means a error in
+ * your code.
+ *
+ * @author Mike Mirzayanov
+ */
+public class IncorrectLogicException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public IncorrectLogicException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public IncorrectLogicException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/InterruptException.java b/code/src/main/java/org/nocturne/exception/InterruptException.java
index 47ffb9c..66828c2 100644
--- a/code/src/main/java/org/nocturne/exception/InterruptException.java
+++ b/code/src/main/java/org/nocturne/exception/InterruptException.java
@@ -1,26 +1,26 @@
-package org.nocturne.exception;
-
-/**
- * Use it to interrupt action method of Component. It differs from AbortException
- * that the component will be rendered using the template (if no skipTemplate() was called).
- *
- * It is preferred to use interrupt() method instead of throwing it manually.
- *
- * @author Mike Mirzayanov
- */
-public class InterruptException extends RuntimeException {
- public InterruptException() {
- }
-
- public InterruptException(String message) {
- super(message);
- }
-
- public InterruptException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public InterruptException(Throwable cause) {
- super(cause);
- }
-}
+package org.nocturne.exception;
+
+/**
+ * Use it to interrupt action method of Component. It differs from AbortException
+ * that the component will be rendered using the template (if no skipTemplate() was called).
+ *
+ * It is preferred to use interrupt() method instead of throwing it manually.
+ *
+ * @author Mike Mirzayanov
+ */
+public class InterruptException extends RuntimeException {
+ public InterruptException() {
+ }
+
+ public InterruptException(String message) {
+ super(message);
+ }
+
+ public InterruptException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InterruptException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/ModuleInitializationException.java b/code/src/main/java/org/nocturne/exception/ModuleInitializationException.java
index 8706772..0ac8227 100644
--- a/code/src/main/java/org/nocturne/exception/ModuleInitializationException.java
+++ b/code/src/main/java/org/nocturne/exception/ModuleInitializationException.java
@@ -1,27 +1,27 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * If nocturne can't initialize module. Possibly, the module
- * configuration has errors.
- *
- * @author Mike Mirzayanov
- */
-public class ModuleInitializationException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public ModuleInitializationException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public ModuleInitializationException(String message, Throwable cause) {
- super(message, cause);
- }
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * If nocturne can't initialize module. Possibly, the module
+ * configuration has errors.
+ *
+ * @author Mike Mirzayanov
+ */
+public class ModuleInitializationException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public ModuleInitializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public ModuleInitializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
\ No newline at end of file
diff --git a/code/src/main/java/org/nocturne/exception/NocturneException.java b/code/src/main/java/org/nocturne/exception/NocturneException.java
index 3cf85d6..9ee1049 100644
--- a/code/src/main/java/org/nocturne/exception/NocturneException.java
+++ b/code/src/main/java/org/nocturne/exception/NocturneException.java
@@ -1,26 +1,26 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * Nocturne fails. I hope you will not see it.
- *
- * @author Mike Mirzayanov
- */
-public class NocturneException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public NocturneException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public NocturneException(String message, Throwable cause) {
- super(message, cause);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * Nocturne fails. I hope you will not see it.
+ *
+ * @author Mike Mirzayanov
+ */
+public class NocturneException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public NocturneException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public NocturneException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/ReflectionException.java b/code/src/main/java/org/nocturne/exception/ReflectionException.java
index da68a11..a927407 100644
--- a/code/src/main/java/org/nocturne/exception/ReflectionException.java
+++ b/code/src/main/java/org/nocturne/exception/ReflectionException.java
@@ -1,27 +1,27 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * If can't invoke operation via reflection. Nocturne uses reflection only
- * in the debug mode.
- *
- * @author Mike Mirzayanov
- */
-public class ReflectionException extends Exception {
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public ReflectionException(String message, Throwable cause) {
- super(message, cause);
- }
-
- /**
- * @param message Error message.
- */
- public ReflectionException(String message) {
- super(message);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * If can't invoke operation via reflection. Nocturne uses reflection only
+ * in the debug mode.
+ *
+ * @author Mike Mirzayanov
+ */
+public class ReflectionException extends Exception {
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public ReflectionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * @param message Error message.
+ */
+ public ReflectionException(String message) {
+ super(message);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/ServletException.java b/code/src/main/java/org/nocturne/exception/ServletException.java
index f5f2ce2..51f8343 100644
--- a/code/src/main/java/org/nocturne/exception/ServletException.java
+++ b/code/src/main/java/org/nocturne/exception/ServletException.java
@@ -1,27 +1,27 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * IOExceptions around servlets are wrapped by this exception.
- * And something other servlet-like exceptions are wrapped by it.
- *
- * @author Mike Mirzayanov
- */
-public class ServletException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public ServletException(String message) {
- super(message);
- }
-
- /**
- * @param message Error message.
- * @param cause Cause.
- */
- public ServletException(String message, Throwable cause) {
- super(message, cause);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * IOExceptions around servlets are wrapped by this exception.
+ * And something other servlet-like exceptions are wrapped by it.
+ *
+ * @author Mike Mirzayanov
+ */
+public class ServletException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public ServletException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param message Error message.
+ * @param cause Cause.
+ */
+ public ServletException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/exception/SessionInvalidatedException.java b/code/src/main/java/org/nocturne/exception/SessionInvalidatedException.java
index 09be17a..0347300 100644
--- a/code/src/main/java/org/nocturne/exception/SessionInvalidatedException.java
+++ b/code/src/main/java/org/nocturne/exception/SessionInvalidatedException.java
@@ -1,13 +1,13 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.exception;
-
-/**
- * Tried to access session variable, but session has been
- * invalidated.
- *
- * @author Mike Mirzayanov
- */
-public class SessionInvalidatedException extends RuntimeException {
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.exception;
+
+/**
+ * Tried to access session variable, but session has been
+ * invalidated.
+ *
+ * @author Mike Mirzayanov
+ */
+public class SessionInvalidatedException extends RuntimeException {
+}
diff --git a/code/src/main/java/org/nocturne/geoip/GeoIpUtil.java b/code/src/main/java/org/nocturne/geoip/GeoIpUtil.java
index 8bdd11f..023ec4f 100644
--- a/code/src/main/java/org/nocturne/geoip/GeoIpUtil.java
+++ b/code/src/main/java/org/nocturne/geoip/GeoIpUtil.java
@@ -1,124 +1,124 @@
-package org.nocturne.geoip;
-
-import com.maxmind.db.CHMCache;
-import com.maxmind.db.Reader;
-import com.maxmind.geoip2.DatabaseReader;
-import com.maxmind.geoip2.exception.GeoIp2Exception;
-import com.maxmind.geoip2.model.CityResponse;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
-import org.nocturne.util.StringUtil;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.nio.file.Files;
-import java.util.*;
-
-/**
- * @author Mike Mirzayanov (mirzayanovmr@gmail.com)
- */
-@SuppressWarnings("WeakerAccess")
-public final class GeoIpUtil {
- private static final Logger logger = Logger.getLogger(GeoIpUtil.class);
-
- private static final DatabaseReader COUNTRY_DETECTION_SERVICE;
- private static final DatabaseReader CITY_DETECTION_SERVICE;
-
- private GeoIpUtil() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * @param ip IPv4 or IPv6 ip-address.
- * @return The ISO two-letter country code of country or "--". Example: RU.
- */
- @Nonnull
- public static String getCountryCodeByIp(@Nonnull String ip) {
- try {
- return COUNTRY_DETECTION_SERVICE.country(InetAddress.getByName(ip)).getCountry().getIsoCode();
- } catch (IOException | GeoIp2Exception | RuntimeException ignored) {
- return "--";
- }
- }
-
- @Nullable
- public static String getCityByIp(@Nonnull String ip) {
- try {
- CityResponse cityResponse = CITY_DETECTION_SERVICE.city(InetAddress.getByName(ip));
- if (cityResponse.getCity().getName() == null) {
- return cityResponse.getCountry().getName();
- } else {
- return cityResponse.getCountry().getName() + ", " + cityResponse.getCity().getName();
- }
- } catch (IOException | GeoIp2Exception | RuntimeException ignored) {
- return null;
- }
- }
-
- @Nonnull
- public static Map getCityByIp(@Nonnull Collection ips) {
- Map result = new HashMap<>();
- for (String ip : ips) {
- String city = getCityByIp(ip);
- if (city != null) {
- result.put(ip, city);
- }
- }
- return result;
- }
-
- /**
- * @param httpServletRequest Http request.
- * @return The ISO two-letter country code of country or "--". Example: RU.
- */
- @Nonnull
- public static String getCountryCode(@Nonnull HttpServletRequest httpServletRequest) {
- String ip = StringUtil.trimToNull(httpServletRequest.getHeader("X-Real-IP"));
- if (ip != null) {
- return getCountryCodeByIp(ip);
- }
-
- ip = StringUtil.trimToNull(httpServletRequest.getRemoteAddr());
- if (ip != null) {
- return getCountryCodeByIp(ip);
- }
-
- return "--";
- }
-
- static {
- String countryResourcePath = "/org/nocturne/geoip2/GeoLite2-Country.mmdb";
-
- try {
- COUNTRY_DETECTION_SERVICE = new DatabaseReader.Builder(GeoIpUtil.class.getResourceAsStream(
- countryResourcePath)).withCache(new CHMCache()).fileMode(Reader.FileMode.MEMORY).build();
- } catch (IOException e) {
- throw new RuntimeException("Can't read resource '" + countryResourcePath + "'.", e);
- }
-
- DatabaseReader cityDatabaseReader = null;
- List citiesPaths = Arrays.asList("/srv/app/GeoLite2-City.mmdb", "C:/Temp/GeoLite2-City.mmdb");
- for (String citiesPath : citiesPaths) {
- if (cityDatabaseReader == null) {
- try (InputStream cityInputStream = Files.newInputStream(new File(citiesPath).toPath())) {
- cityDatabaseReader = new DatabaseReader.Builder(cityInputStream)
- .withCache(new CHMCache()).fileMode(Reader.FileMode.MEMORY).build();
- logger.info("GeoLite2-City loaded from '" + citiesPath + "'.");
- } catch (Exception e) {
- logger.info("Can't find \"" + citiesPath + "\".");
- }
- }
- }
-
- if (cityDatabaseReader == null) {
- logger.warn("Can't find GeoLite2-City.mmdb in paths: " + StringUtils.join(citiesPaths, ", ") + ".");
- }
-
- CITY_DETECTION_SERVICE = cityDatabaseReader;
- }
-}
+package org.nocturne.geoip;
+
+import com.maxmind.db.CHMCache;
+import com.maxmind.db.Reader;
+import com.maxmind.geoip2.DatabaseReader;
+import com.maxmind.geoip2.exception.GeoIp2Exception;
+import com.maxmind.geoip2.model.CityResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.nocturne.util.StringUtil;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.util.*;
+
+/**
+ * @author Mike Mirzayanov (mirzayanovmr@gmail.com)
+ */
+@SuppressWarnings("WeakerAccess")
+public final class GeoIpUtil {
+ private static final Logger logger = Logger.getLogger(GeoIpUtil.class);
+
+ private static final DatabaseReader COUNTRY_DETECTION_SERVICE;
+ private static final DatabaseReader CITY_DETECTION_SERVICE;
+
+ private GeoIpUtil() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param ip IPv4 or IPv6 ip-address.
+ * @return The ISO two-letter country code of country or "--". Example: RU.
+ */
+ @Nonnull
+ public static String getCountryCodeByIp(@Nonnull String ip) {
+ try {
+ return COUNTRY_DETECTION_SERVICE.country(InetAddress.getByName(ip)).getCountry().getIsoCode();
+ } catch (IOException | GeoIp2Exception | RuntimeException ignored) {
+ return "--";
+ }
+ }
+
+ @Nullable
+ public static String getCityByIp(@Nonnull String ip) {
+ try {
+ CityResponse cityResponse = CITY_DETECTION_SERVICE.city(InetAddress.getByName(ip));
+ if (cityResponse.getCity().getName() == null) {
+ return cityResponse.getCountry().getName();
+ } else {
+ return cityResponse.getCountry().getName() + ", " + cityResponse.getCity().getName();
+ }
+ } catch (IOException | GeoIp2Exception | RuntimeException ignored) {
+ return null;
+ }
+ }
+
+ @Nonnull
+ public static Map getCityByIp(@Nonnull Collection ips) {
+ Map result = new HashMap<>();
+ for (String ip : ips) {
+ String city = getCityByIp(ip);
+ if (city != null) {
+ result.put(ip, city);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @param httpServletRequest Http request.
+ * @return The ISO two-letter country code of country or "--". Example: RU.
+ */
+ @Nonnull
+ public static String getCountryCode(@Nonnull HttpServletRequest httpServletRequest) {
+ String ip = StringUtil.trimToNull(httpServletRequest.getHeader("X-Real-IP"));
+ if (ip != null) {
+ return getCountryCodeByIp(ip);
+ }
+
+ ip = StringUtil.trimToNull(httpServletRequest.getRemoteAddr());
+ if (ip != null) {
+ return getCountryCodeByIp(ip);
+ }
+
+ return "--";
+ }
+
+ static {
+ String countryResourcePath = "/org/nocturne/geoip2/GeoLite2-Country.mmdb";
+
+ try {
+ COUNTRY_DETECTION_SERVICE = new DatabaseReader.Builder(GeoIpUtil.class.getResourceAsStream(
+ countryResourcePath)).withCache(new CHMCache()).fileMode(Reader.FileMode.MEMORY).build();
+ } catch (IOException e) {
+ throw new RuntimeException("Can't read resource '" + countryResourcePath + "'.", e);
+ }
+
+ DatabaseReader cityDatabaseReader = null;
+ List citiesPaths = Arrays.asList("/srv/app/GeoLite2-City.mmdb", "C:/Temp/GeoLite2-City.mmdb");
+ for (String citiesPath : citiesPaths) {
+ if (cityDatabaseReader == null) {
+ try (InputStream cityInputStream = Files.newInputStream(new File(citiesPath).toPath())) {
+ cityDatabaseReader = new DatabaseReader.Builder(cityInputStream)
+ .withCache(new CHMCache()).fileMode(Reader.FileMode.MEMORY).build();
+ logger.info("GeoLite2-City loaded from '" + citiesPath + "'.");
+ } catch (Exception e) {
+ logger.info("Can't find \"" + citiesPath + "\".");
+ }
+ }
+ }
+
+ if (cityDatabaseReader == null) {
+ logger.warn("Can't find GeoLite2-City.mmdb in paths: " + StringUtils.join(citiesPaths, ", ") + ".");
+ }
+
+ CITY_DETECTION_SERVICE = cityDatabaseReader;
+ }
+}
diff --git a/code/src/main/java/org/nocturne/link/Link.java b/code/src/main/java/org/nocturne/link/Link.java
index 6adaed9..39fbeff 100644
--- a/code/src/main/java/org/nocturne/link/Link.java
+++ b/code/src/main/java/org/nocturne/link/Link.java
@@ -1,103 +1,103 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.link;
-
-import com.google.common.base.Strings;
-
-import javax.annotation.Nonnull;
-import java.lang.annotation.*;
-
-/**
- * Use it to specify link pattern for page.
- *
- * @author Mike Mirzayanov
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Link {
- /**
- * @return
- * Use ";" to separate links. Do not use slash as a first character.
- *
- *
- * Example:
- * - "user/{login};profile" - first link contains 'login' parameter, second link contains no parameters;
- * - "user/{login(!blank,alphanumeric):!admin}" - link contains 'login' parameter with restrictions
- * (should not be blank and should contain only letters and digits, also should not be equal to 'admin');
- * - "book/{bookId(long,positive)}" - link contains 'bookId' parameter with restrictions (should be a
- * positive long integer);
- * - "action/{action:purchase,sell,!action}" - link contains 'action' parameter with restrictions (should
- * be either equal to 'purchase' or 'sell' and should not be equal to 'action').
- *
- *
- * List of restrictions: null, empty, blank, alpha, numeric, alphanumeric, byte, short, int, long, float,
- * double, positive, nonpositive, negative, nonnegative, zero, nonzero.
- *
- */
- String value();
-
- /**
- * @return Link name: use it together with getRequest().getAttribute("nocturne.current-page-link").
- */
- String name() default "";
-
- /**
- * @return Default action for page, it will be invoked if no action specified as request parameter.
- */
- String action() default "";
-
- /**
- * @return You can mark link usages with some classes. For example, it
- * could be menu items or layout types.
- */
- Class extends Type>[] types() default {};
-
- /**
- * @return List of interceptors to skip.
- */
- String[] skipInterceptors() default {};
-
- /**
- * Marker interface for type of link.
- */
- interface Type {
- }
-
- class Builder {
- public static Link newLink(@Nonnull String value, @Nonnull String name,
- @Nonnull String action, @Nonnull Class extends Type>[] types) {
- return new Link() {
- @Override
- public String value() {
- return value;
- }
-
- @Override
- public String name() {
- return Strings.nullToEmpty(name);
- }
-
- @Override
- public String action() {
- return Strings.nullToEmpty(action);
- }
-
- @Override
- public Class extends Type>[] types() {
- return types;
- }
-
- @Override
- public Class extends Annotation> annotationType() {
- return Link.class;
- }
-
- @Override
- public String[] skipInterceptors() {
- return new String[0];
- }
- };
- }
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.link;
+
+import com.google.common.base.Strings;
+
+import javax.annotation.Nonnull;
+import java.lang.annotation.*;
+
+/**
+ * Use it to specify link pattern for page.
+ *
+ * @author Mike Mirzayanov
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Link {
+ /**
+ * @return
+ * Use ";" to separate links. Do not use slash as a first character.
+ *
+ *
+ * Example:
+ * - "user/{login};profile" - first link contains 'login' parameter, second link contains no parameters;
+ * - "user/{login(!blank,alphanumeric):!admin}" - link contains 'login' parameter with restrictions
+ * (should not be blank and should contain only letters and digits, also should not be equal to 'admin');
+ * - "book/{bookId(long,positive)}" - link contains 'bookId' parameter with restrictions (should be a
+ * positive long integer);
+ * - "action/{action:purchase,sell,!action}" - link contains 'action' parameter with restrictions (should
+ * be either equal to 'purchase' or 'sell' and should not be equal to 'action').
+ *
+ *
+ * List of restrictions: null, empty, blank, alpha, numeric, alphanumeric, byte, short, int, long, float,
+ * double, positive, nonpositive, negative, nonnegative, zero, nonzero.
+ *
+ */
+ String value();
+
+ /**
+ * @return Link name: use it together with getRequest().getAttribute("nocturne.current-page-link").
+ */
+ String name() default "";
+
+ /**
+ * @return Default action for page, it will be invoked if no action specified as request parameter.
+ */
+ String action() default "";
+
+ /**
+ * @return You can mark link usages with some classes. For example, it
+ * could be menu items or layout types.
+ */
+ Class extends Type>[] types() default {};
+
+ /**
+ * @return List of interceptors to skip.
+ */
+ String[] skipInterceptors() default {};
+
+ /**
+ * Marker interface for type of link.
+ */
+ interface Type {
+ }
+
+ class Builder {
+ public static Link newLink(@Nonnull String value, @Nonnull String name,
+ @Nonnull String action, @Nonnull Class extends Type>[] types) {
+ return new Link() {
+ @Override
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public String name() {
+ return Strings.nullToEmpty(name);
+ }
+
+ @Override
+ public String action() {
+ return Strings.nullToEmpty(action);
+ }
+
+ @Override
+ public Class extends Type>[] types() {
+ return types;
+ }
+
+ @Override
+ public Class extends Annotation> annotationType() {
+ return Link.class;
+ }
+
+ @Override
+ public String[] skipInterceptors() {
+ return new String[0];
+ }
+ };
+ }
+ }
+}
diff --git a/code/src/main/java/org/nocturne/link/LinkDirective.java b/code/src/main/java/org/nocturne/link/LinkDirective.java
index addfb4b..951b5ff 100644
--- a/code/src/main/java/org/nocturne/link/LinkDirective.java
+++ b/code/src/main/java/org/nocturne/link/LinkDirective.java
@@ -1,66 +1,66 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.link;
-
-import freemarker.core.Environment;
-import freemarker.template.*;
-
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Generates page link.
- *
- * @author Mike Mirzayanov
- */
-@SuppressWarnings("Singleton")
-public class LinkDirective implements TemplateDirectiveModel {
- private static final LinkDirective INSTANCE = new LinkDirective();
-
- private LinkDirective() {
- // No operations.
- }
-
- @Override
- public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
- throws TemplateException, IOException {
- if (!params.containsKey("name")) {
- throw new TemplateModelException("LinkDirective directive expects name parameter.");
- }
-
- String name = params.remove("name").toString();
-
- if (loopVars.length != 0) {
- throw new TemplateModelException("LinkDirective directive doesn't allow loop variables.");
- }
-
- Object linkNameValue = params.remove("linkName");
- String linkName = linkNameValue == null ? null : linkNameValue.toString();
-
- if (body == null) {
- if (params.containsKey("value")) {
- String value = params.get("value").toString();
- params.remove("value");
- String a = String.format("%s ", getLink(params, name, linkName), value);
- env.getOut().write(a);
- } else {
- env.getOut().write(getLink(params, name, linkName));
- }
- } else {
- throw new TemplateModelException("Body is not expected for LinkDirective directive.");
- }
- }
-
- @SuppressWarnings("unchecked")
- private static String getLink(Map params, String name, String linkName) {
- return linkName == null ? Links.getLinkByMap(name, params) : Links.getLinkByMap(name, linkName, params);
- }
-
- /**
- * @return Returns the only directive instance.
- */
- public static LinkDirective getInstance() {
- return INSTANCE;
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.link;
+
+import freemarker.core.Environment;
+import freemarker.template.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Generates page link.
+ *
+ * @author Mike Mirzayanov
+ */
+@SuppressWarnings("Singleton")
+public class LinkDirective implements TemplateDirectiveModel {
+ private static final LinkDirective INSTANCE = new LinkDirective();
+
+ private LinkDirective() {
+ // No operations.
+ }
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ if (!params.containsKey("name")) {
+ throw new TemplateModelException("LinkDirective directive expects name parameter.");
+ }
+
+ String name = params.remove("name").toString();
+
+ if (loopVars.length != 0) {
+ throw new TemplateModelException("LinkDirective directive doesn't allow loop variables.");
+ }
+
+ Object linkNameValue = params.remove("linkName");
+ String linkName = linkNameValue == null ? null : linkNameValue.toString();
+
+ if (body == null) {
+ if (params.containsKey("value")) {
+ String value = params.get("value").toString();
+ params.remove("value");
+ String a = String.format("%s ", getLink(params, name, linkName), value);
+ env.getOut().write(a);
+ } else {
+ env.getOut().write(getLink(params, name, linkName));
+ }
+ } else {
+ throw new TemplateModelException("Body is not expected for LinkDirective directive.");
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static String getLink(Map params, String name, String linkName) {
+ return linkName == null ? Links.getLinkByMap(name, params) : Links.getLinkByMap(name, linkName, params);
+ }
+
+ /**
+ * @return Returns the only directive instance.
+ */
+ public static LinkDirective getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/code/src/main/java/org/nocturne/link/LinkMatchResult.java b/code/src/main/java/org/nocturne/link/LinkMatchResult.java
index f8cd86a..07a045a 100644
--- a/code/src/main/java/org/nocturne/link/LinkMatchResult.java
+++ b/code/src/main/java/org/nocturne/link/LinkMatchResult.java
@@ -1,83 +1,83 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.link;
-
-import org.nocturne.main.Page;
-
-import java.util.Map;
-
-/**
- * It is the result for method Links.match(String). Contains information about
- * matched page and matching itself.
- *
- * @author Mike Mirzayanov
- * @author Andrew Lazarev
- */
-public class LinkMatchResult {
- /**
- * Page matched by given link.
- */
- private final Class extends Page> pageClass;
-
- /**
- * Part of link value, matched pattern.
- */
- private final String pattern;
-
- /**
- * Attributes extracted from given link.
- */
- private final Map attributes;
-
- /**
- * Matched link directive.
- */
- private final Link link;
-
- /**
- * Constructor LinkMatchResult creates a new LinkMatchResult instance.
- *
- * @param pageClass Matched page class.
- * @param pattern Matched pattern (link value).
- * @param attributes attributes extracted from given matching.
- * @param link Matched @Link instance.
- */
- public LinkMatchResult(Class extends Page> pageClass, String pattern, Map attributes, Link link) {
- this.pageClass = pageClass;
- this.pattern = pattern;
- this.attributes = attributes;
- this.link = link;
- }
-
- /**
- * @return Matched page class.
- */
- public Class extends Page> getPageClass() {
- return pageClass;
- }
-
- /**
- * @return Pattern which was matched.
- */
- public String getPattern() {
- return pattern;
- }
-
- /**
- * @return Attributes which was extracted from matching.
- * For example, it pattern="user/{login}" and Links.match()
- * argument is "user/mike"
- * then the returned map will be {@literal {"login"=>"mike"}}.
- */
- public Map getAttributes() {
- return attributes;
- }
-
- /**
- * @return Returns matched link.
- */
- public Link getLink() {
- return link;
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.link;
+
+import org.nocturne.main.Page;
+
+import java.util.Map;
+
+/**
+ * It is the result for method Links.match(String). Contains information about
+ * matched page and matching itself.
+ *
+ * @author Mike Mirzayanov
+ * @author Andrew Lazarev
+ */
+public class LinkMatchResult {
+ /**
+ * Page matched by given link.
+ */
+ private final Class extends Page> pageClass;
+
+ /**
+ * Part of link value, matched pattern.
+ */
+ private final String pattern;
+
+ /**
+ * Attributes extracted from given link.
+ */
+ private final Map attributes;
+
+ /**
+ * Matched link directive.
+ */
+ private final Link link;
+
+ /**
+ * Constructor LinkMatchResult creates a new LinkMatchResult instance.
+ *
+ * @param pageClass Matched page class.
+ * @param pattern Matched pattern (link value).
+ * @param attributes attributes extracted from given matching.
+ * @param link Matched @Link instance.
+ */
+ public LinkMatchResult(Class extends Page> pageClass, String pattern, Map attributes, Link link) {
+ this.pageClass = pageClass;
+ this.pattern = pattern;
+ this.attributes = attributes;
+ this.link = link;
+ }
+
+ /**
+ * @return Matched page class.
+ */
+ public Class extends Page> getPageClass() {
+ return pageClass;
+ }
+
+ /**
+ * @return Pattern which was matched.
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * @return Attributes which was extracted from matching.
+ * For example, it pattern="user/{login}" and Links.match()
+ * argument is "user/mike"
+ * then the returned map will be {@literal {"login"=>"mike"}}.
+ */
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * @return Returns matched link.
+ */
+ public Link getLink() {
+ return link;
+ }
+}
diff --git a/code/src/main/java/org/nocturne/link/LinkSet.java b/code/src/main/java/org/nocturne/link/LinkSet.java
index 7732f82..d851828 100644
--- a/code/src/main/java/org/nocturne/link/LinkSet.java
+++ b/code/src/main/java/org/nocturne/link/LinkSet.java
@@ -1,19 +1,19 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.link;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @author Mike Mirzayanov
- */
-
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface LinkSet {
- Link[] value() default {};
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.link;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Mike Mirzayanov
+ */
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface LinkSet {
+ Link[] value() default {};
+}
diff --git a/code/src/main/java/org/nocturne/link/Links.java b/code/src/main/java/org/nocturne/link/Links.java
index 78a1ebb..983106f 100644
--- a/code/src/main/java/org/nocturne/link/Links.java
+++ b/code/src/main/java/org/nocturne/link/Links.java
@@ -1,1109 +1,1109 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.link;
-
-import freemarker.template.TemplateModel;
-import freemarker.template.TemplateModelException;
-import freemarker.template.TemplateSequenceModel;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.mutable.MutableBoolean;
-import org.nocturne.annotation.Name;
-import org.nocturne.collection.SingleEntryList;
-import org.nocturne.exception.ConfigurationException;
-import org.nocturne.exception.NocturneException;
-import org.nocturne.main.ApplicationContext;
-import org.nocturne.main.Page;
-import org.nocturne.util.StringUtil;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Array;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- *
- * Handles link pattern methods.
- * Each page should have @Link annotation to specify its link.
- * It can use parameters (templates), like "profile/{handle}".
- *
- *
- * If you want to redirect to SomePage, use abortWithRedirect(Links.getLink(SomePage.class)) or
- * abortWithRedirect(SomePage.class).
- *
- *
- * @author Mike Mirzayanov
- */
-public class Links {
- private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Links.class);
-
- private static final Lock addLinkLock = new ReentrantLock();
-
- private static final int INTERCEPTOR_MAX_PERMIT_COUNT = 8 * Runtime.getRuntime().availableProcessors();
- private static final Semaphore interceptorSemaphore = new Semaphore(INTERCEPTOR_MAX_PERMIT_COUNT);
- private static final Map interceptorByNameMap = new LinkedHashMap<>();
-
-
- /**
- * Stores maps for each page class. Each map contains single patterns as keys
- * and Link instances as values.
- */
- private static final ConcurrentMap, Map> linksByPage = new ConcurrentHashMap<>();
-
- /**
- * Stores page classes by their names.
- */
- private static final ConcurrentMap> classesByName = new ConcurrentHashMap<>();
-
- /**
- * Stores link sections by links.
- */
- private static final ConcurrentMap> sectionsByLinkText = new ConcurrentHashMap<>();
-
- private static List getLinksViaReflection(Class extends Page> clazz) {
- List result = new ArrayList<>();
- Link link = clazz.getAnnotation(Link.class);
- if (link != null) {
- result.add(link);
- }
- LinkSet linkSet = clazz.getAnnotation(LinkSet.class);
- if (linkSet != null) {
- result.addAll(Arrays.asList(linkSet.value()));
- }
- return result;
- }
-
- /**
- * Use the method to get page link name. Do not use page.getClass().getSimpleName() because of two reasons:
- * - page can have @Name annotation,
- * - page can be actually inherited from expected ConcretePage class because of IoC.
- *
- * @param page Page instance.
- * @return Page link name.
- */
- public static String getLinkName(@Nonnull Page page) {
- return getLinkName(page.getClass());
- }
-
- /**
- * Use the method to get page class link name. Do not use pageClass.getSimpleName() because of two reasons:
- * - page class can have @Name annotation,
- * - page class can be actually inherited from expected ConcretePage class because of IoC.
- *
- * @param pageClass Page class.
- * @return Page link name.
- */
- public static String getLinkName(@Nonnull Class extends Page> pageClass) {
- Class> clazz = pageClass;
-
- while (clazz != null && clazz.getAnnotation(Link.class) == null && clazz.getAnnotation(LinkSet.class) == null) {
- clazz = clazz.getSuperclass();
- }
-
- if (clazz == null) {
- logger.error("Page class should have @Link or @LinkSet annotation, but "
- + pageClass.getName() + " hasn't.");
- throw new NocturneException("Page class should have @Link or @LinkSet annotation, but "
- + pageClass.getName() + " hasn't.");
- }
-
- Name name = clazz.getAnnotation(Name.class);
- if (name == null) {
- return clazz.getSimpleName();
- } else {
- return name.value();
- }
- }
-
- /**
- * @param clazz Page class to be added into Links.
- * After it you can get it's link via getLink, or using @link directive
- * from template. Link may contain template sections, like "profile/{handle}".
- * @param linkSet List of links to be added for class {@code clazz}.
- */
- public static void add(Class extends Page> clazz, List linkSet) {
- addLinkLock.lock();
-
- try {
- String name = getLinkName(clazz);
- if (classesByName.containsKey(name) && !clazz.equals(classesByName.get(name))) {
- logger.error("Can't add page which is not unique by it's name: " + clazz.getName() + '.');
- throw new ConfigurationException("Can't add page which is not unique by it's name: "
- + clazz.getName() + '.');
- }
- classesByName.put(name, clazz);
-
- Map links = getLinksByPageClass(clazz);
- if (links == null) {
- // It is important that used synchronizedMap, because of "synchronized(links) {..}" later in code.
- links = Collections.synchronizedMap(new LinkedHashMap());
- }
-
- for (Link link : linkSet) {
- String[] pageLinks = StringUtil.Patterns.SEMICOLON_PATTERN.split(link.value());
- for (String pageLink : pageLinks) {
- if (!sectionsByLinkText.containsKey(pageLink)) {
- sectionsByLinkText.putIfAbsent(pageLink, parseLinkToLinkSections(pageLink));
- }
-
- for (Map linkMap : linksByPage.values()) {
- if (linkMap.containsKey(pageLink)) {
- logger.error("Page link \"" + pageLink + "\" already registered.");
- throw new ConfigurationException("Page link \"" + pageLink + "\" already registered.");
- }
- }
- if (links.containsKey(pageLink)) {
- logger.error("Page link \"" + pageLink + "\" already registered.");
- throw new ConfigurationException("Page link \"" + pageLink + "\" already registered.");
- }
-
- links.put(pageLink, link);
- }
- }
-
- linksByPage.put(clazz, links);
- } finally {
- addLinkLock.unlock();
- }
- }
-
- /**
- * @param clazz Page class to be added into Links.
- * After it you can get it's link via getLink, or using @link directive
- * from template. Link may contain template sections, like "profile/{handle}".
- */
- public static void add(Class extends Page> clazz) {
- List linkSet = getLinksViaReflection(clazz);
- if (linkSet.isEmpty()) {
- logger.error("Can't find link for page " + clazz.getName() + '.');
- throw new ConfigurationException("Can't find link for page " + clazz.getName() + '.');
- }
-
- add(clazz, linkSet);
- }
-
- /**
- * @param clazz Page class.
- * @param linkName desired {@link Link#name() name} of the link
- * @param params parameters for substitution (for example link "profile/{handle}"
- * may use "handle" key in the map.
- * @return link for page. If there many links for page, returns one of them, which matches better
- * @throws NoSuchLinkException if no such link exists
- */
- @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"})
- public static String getLinkByMap(Class extends Page> clazz, @Nullable String linkName, Map params) {
- MutableBoolean multiValueParams = new MutableBoolean();
- Map> nonEmptyParams = getNonEmptyParams(params, multiValueParams);
-
- int bestMatchedCount = -1;
- List bestMatchedLinkSections = null;
- Link bestMatchedLink = null;
-
- for (Map.Entry entry : getLinksByPageClass(clazz).entrySet()) {
- if (linkName != null && !linkName.isEmpty() && !linkName.equals(entry.getValue().name())) {
- continue;
- }
-
- List sections = sectionsByLinkText.get(entry.getKey());
- boolean matched = true;
- int matchedCount = 0;
- for (LinkSection section : sections) {
- if (section.isParameter()) {
- ++matchedCount;
- List values = nonEmptyParams.get(section.getParameterName());
- String value = values == null ? null : values.get(0);
- if (value == null || !section.isSuitable(value)) {
- matched = false;
- break;
- }
- }
- }
- if (matched && matchedCount > bestMatchedCount) {
- bestMatchedCount = matchedCount;
- bestMatchedLinkSections = sections;
- bestMatchedLink = entry.getValue();
- }
- }
-
- if (bestMatchedLinkSections == null) {
- if (linkName == null || linkName.isEmpty()) {
- throw new NoSuchLinkException("Can't find link for page " + clazz.getName() + '.');
- } else {
- throw new NoSuchLinkException("Can't find link with name '"
- + linkName + "' for page " + clazz.getName() + '.');
- }
- }
-
- StringBuilder result = new StringBuilder(ApplicationContext.getInstance().getContextPath());
- Set usedKeys = new HashSet<>();
-
- for (LinkSection section : bestMatchedLinkSections) {
- String item;
-
- if (section.isParameter()) {
- usedKeys.add(section.getParameterName());
- item = nonEmptyParams.get(section.getParameterName()).get(0);
- } else {
- item = section.getValue();
- }
-
- result.append('/').append(item);
- }
-
- if (nonEmptyParams.size() > usedKeys.size() || multiValueParams.isTrue()) {
- boolean first = true;
- for (Map.Entry> entry : nonEmptyParams.entrySet()) {
- List values = entry.getValue();
- int valueCount = values.size();
- int startIndex = valueCount;
-
- if (usedKeys.contains(entry.getKey())) {
- if (valueCount > 1) {
- startIndex = 1;
- }
- } else {
- startIndex = 0;
- }
-
- for (int i = startIndex; i < valueCount; ++i) {
- if (first) {
- result.append('?');
- first = false;
- } else {
- result.append('&');
- }
-
- try {
- result.append(entry.getKey()).append('=').append(URLEncoder.encode(values.get(i), StandardCharsets.UTF_8.name()));
- } catch (UnsupportedEncodingException e) {
- // No operations.
- }
- }
- }
- }
-
- String linkResult = result.toString();
-
- interceptorSemaphore.acquireUninterruptibly();
- try {
- for (Map.Entry e : interceptorByNameMap.entrySet()) {
- boolean skip = false;
- for (String skipInterceptor : bestMatchedLink.skipInterceptors()) {
- if (skipInterceptor.equals(e.getKey())) {
- skip = true;
- break;
- }
- }
- if (!skip) {
- linkResult = e.getValue().postprocess(linkResult, clazz, linkName, params);
- }
- }
- } finally {
- interceptorSemaphore.release();
- }
-
- return linkResult;
- }
-
- private static Map> getNonEmptyParams(Map params, MutableBoolean multiValueParams) {
- multiValueParams.setValue(false);
- Map> nonEmptyParams = new LinkedHashMap<>();
-
- for (Map.Entry entry : params.entrySet()) {
- Object value = entry.getValue();
- if (!isMissingValue(value)) {
- List list = toStringList(value);
- int count = list.size();
-
- if (count > 0) {
- nonEmptyParams.put(entry.getKey(), list);
-
- if (count > 1) {
- multiValueParams.setValue(true);
- }
- }
- }
- }
-
- return nonEmptyParams;
- }
-
- @Nonnull
- private static List toStringList(@Nonnull Object value) {
- if (value instanceof TemplateSequenceModel) {
- return toStringList((TemplateSequenceModel) value);
- } else if (value instanceof Collection) {
- return toStringList((Collection) value);
- } else if (value.getClass().isArray()) {
- int count = Array.getLength(value);
- List list = new ArrayList<>(count);
-
- for (int i = 0; i < count; ++i) {
- Object item = Array.get(value, i);
- if (item != null) {
- list.add(item.toString());
- }
- }
-
- return list;
- } else {
- return new SingleEntryList<>(value.toString());
- }
- }
-
- private static List toStringList(@Nonnull TemplateSequenceModel sequence) {
- int count = getSize(sequence);
- List list = new ArrayList<>(count);
-
- for (int i = 0; i < count; ++i) {
- TemplateModel item;
- try {
- item = sequence.get(i);
- } catch (TemplateModelException e) {
- logger.error("Can't get item of Freemarker sequence.", e);
- throw new NocturneException("Can't get item of Freemarker sequence.", e);
- }
-
- if (item != null) {
- list.add(item.toString());
- }
- }
-
- return list;
- }
-
- private static List toStringList(@Nonnull Collection collection) {
- List list = new ArrayList<>(collection.size());
-
- for (Object item : collection) {
- if (item != null) {
- list.add(item.toString());
- }
- }
-
- return list;
- }
-
- private static int getSize(@Nonnull TemplateSequenceModel sequence) {
- try {
- return sequence.size();
- } catch (TemplateModelException e) {
- logger.error("Can't get size of Freemarker sequence.", e);
- throw new NocturneException("Can't get size of Freemarker sequence.", e);
- }
- }
-
- /**
- * @param clazz Page class.
- * @param params parameters for substitution (for example link "profile/{handle}"
- * may use "handle" key in the map.
- * @return Returns link for page. If there many links for page, returns
- * one of them, which matches better. Throws NoSuchLinkException
- * if no such link exists.
- */
- public static String getLinkByMap(Class extends Page> clazz, Map params) {
- return getLinkByMap(clazz, null, params);
- }
-
- /**
- * @param name Page name.
- * @param linkName desired {@link Link#name() name} of the link
- * @param params parameters for substitution (for example link "profile/{handle}"
- * may use "handle" key in the map.
- * @return Returns link for page. If there many links for page, returns
- * one of them, which matches better. Throws NoSuchLinkException
- * if no such link exists.
- */
- public static String getLinkByMap(String name, @Nullable String linkName, Map params) {
- Class extends Page> clazz = classesByName.get(name);
-
- if (clazz == null) {
- logger.error("Can't find link for page " + name + '.');
- throw new NoSuchLinkException("Can't find link for page " + name + '.');
- } else {
- return getLinkByMap(clazz, linkName, params);
- }
- }
-
- /**
- * @param name Page name.
- * @param params parameters for substitution (for example link "profile/{handle}"
- * may use "handle" key in the map.
- * @return Returns link for page. If there many links for page, returns
- * one of them, which matches better. Throws NoSuchLinkException
- * if no such link exists.
- */
- public static String getLinkByMap(String name, Map params) {
- return getLinkByMap(name, null, params);
- }
-
- private static boolean isMissingValue(Object value) {
- if (value == null) {
- return true;
- }
-
- if (value instanceof TemplateSequenceModel) {
- return getSize((TemplateSequenceModel) value) <= 0;
- } else if (value instanceof Collection) {
- return ((Collection) value).isEmpty();
- } else if (value.getClass().isArray()) {
- return Array.getLength(value) <= 0;
- } else {
- return value.toString().isEmpty();
- }
- }
-
- /**
- * @param params Array of values.
- * @return Correspondent map.
- */
- private static Map convertArrayToMap(Object... params) {
- int paramCount = params.length;
-
- if (paramCount == 0) {
- return Collections.emptyMap();
- }
-
- if (paramCount % 2 != 0) {
- logger.error("Params should contain even number of elements.");
- throw new IllegalArgumentException("Params should contain even number of elements.");
- }
-
- Map map = new LinkedHashMap<>();
-
- for (int paramIndex = 0; paramIndex < paramCount; paramIndex += 2) {
- map.put(params[paramIndex].toString(), params[paramIndex + 1]);
- }
-
- return map;
- }
-
- /**
- * @param pageClass Page class.
- * @return link for page. If there many links for page, returns one of them, which matches better
- * @throws NoSuchLinkException if no such link exists
- */
- public static String getLink(Class extends Page> pageClass) {
- return getLinkByMap(pageClass, null, Collections.emptyMap());
- }
-
- /**
- * @param pageClass Page class.
- * @param params Even length sequence of Objects. Even elements mean keys and odd
- * values of parameters map. For example ["handle", "MikeMirzayanov", "topic", 123]
- * means map {@literal ["handle" => "MikeMirzayanov", "topic" => 123]}. Method skips params with null value.
- * @return link for page. If there many links for page, returns one of them, which matches better
- * @throws NoSuchLinkException if no such link exists
- */
- public static String getLink(Class extends Page> pageClass, Object... params) {
- return getLinkByMap(pageClass, null, convertArrayToMap(params));
- }
-
- /**
- * @param name Page name.
- * @param params Even length sequence of Objects. Even elements mean keys and odd
- * values of parameters map. For example ["handle", "MikeMirzayanov", "topic", 123]
- * means map {@literal ["handle" => "MikeMirzayanov", "topic" => 123]}. Method skips params with null value.
- * @return link for page. If there many links for page, returns one of them, which matches better
- * @throws NoSuchLinkException if no such link exists
- */
- public static String getLink(String name, Object... params) {
- return getLinkByMap(name, null, convertArrayToMap(params));
- }
-
- /**
- * @param link Relative link to the page started from "/".
- * For example, "/profile/MikeMirzayanov".
- * @return Result instance or {@code null} if not found.
- */
- public static LinkMatchResult match(String link) {
- // Remove anchor.
- if (link.contains("#")) {
- link = link.substring(0, link.lastIndexOf('#'));
- }
-
- // Remove query string.
- if (link.contains("?")) {
- link = link.substring(0, link.lastIndexOf('?'));
- }
-
- if (!link.startsWith("/")) {
- logger.error("Link \"" + link + "\" doesn't start with '/'.");
- throw new IllegalArgumentException("Link \"" + link + "\" doesn't start with '/'.");
- }
-
- String[] linkTokens = StringUtil.Patterns.SLASH_PATTERN.split(link.substring(1));
-
- for (Map.Entry, Map> listEntry : linksByPage.entrySet()) {
- Map patterns = listEntry.getValue();
- if (patterns == null) {
- continue;
- }
-
- //noinspection SynchronizationOnLocalVariableOrMethodParameter
- synchronized (patterns) {
- for (Map.Entry patternEntry : patterns.entrySet()) {
- String linkText = patternEntry.getKey();
- Map attrs = match(linkTokens, linkText);
-
- if (attrs != null) {
- return new LinkMatchResult(listEntry.getKey(), linkText, attrs, patternEntry.getValue());
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * @param linkTokens For example, ["profile", "MikeMirzayanov"] for requested link "/profile/MikeMirzayanov".
- * @param linkText Link pattern, like "profile/{handle}".
- * @return Correspondent params map or {@code null} if not matched.
- */
- private static Map match(String[] linkTokens, String linkText) {
- List sections = sectionsByLinkText.get(linkText);
- if (sections == null) {
- logger.error("Can't find sections for linkText=\"" + linkText + "\".");
- throw new NocturneException("Can't find sections for linkText=\"" + linkText + "\".");
- }
-
- int linkTokenCount = linkTokens.length;
-
- if (linkTokenCount == sections.size()) {
- Map attrs = new HashMap<>();
-
- for (int linkTokenIndex = 0; linkTokenIndex < linkTokenCount; ++linkTokenIndex) {
- LinkSection section = sections.get(linkTokenIndex);
-
- if (section.isParameter()) {
- if (!section.isSuitable(linkTokens[linkTokenIndex])) {
- return null;
- }
- attrs.put(section.getParameterName(), linkTokens[linkTokenIndex]);
- } else {
- if (!section.getValue().equals(linkTokens[linkTokenIndex])) {
- return null;
- }
- }
- }
-
- return attrs;
- } else {
- return null;
- }
- }
-
- /**
- * If case of getLink-like methods if they can't find requested link.
- */
- @SuppressWarnings({"DeserializableClassInSecureContext", "UncheckedExceptionClass"})
- public static class NoSuchLinkException extends RuntimeException {
- /**
- * @param message Error message.
- */
- public NoSuchLinkException(String message) {
- super(message);
- }
- }
-
- private static List parseLinkToLinkSections(String linkText) {
- if (linkText == null || linkText.startsWith("/") || linkText.endsWith("/")) {
- logger.error("Page link has illegal format, use links like 'home', 'page/{index}', " +
- "'page/{index(long,positive):1,2,3}', 'section/{name(string,!blank):!a,b,c}'.");
- throw new ConfigurationException("Page link has illegal format, use links like 'home', 'page/{index}', " +
- "'page/{index(long,positive):1,2,3}', 'section/{name(string,!blank):!a,b,c}'."
- );
- }
-
- String[] sections = StringUtil.Patterns.SLASH_PATTERN.split(linkText);
- List linkSections = new ArrayList<>(sections.length);
- for (String section : sections) {
- linkSections.add(new LinkSection(section));
- }
-
- return linkSections;
- }
-
- private static final class LinkSection {
- private final String section;
- private final boolean parameter;
- private final String value;
- private final String parameterName;
- private final List parameterRestrictions;
- private final Set allowedParameterValues;
- private final Set forbiddenParameterValues;
-
- /**
- * @param section Each part of link, i.e. "home" from link "test/home"
- */
- @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod", "OverlyNestedMethod"})
- private LinkSection(String section) {
- section = StringUtil.trim(section);
- this.section = section;
-
- if (section.startsWith("{") && section.endsWith("}")) {
- parameter = true;
- value = null;
-
- String[] parts = StringUtil.Patterns.COLON_PATTERN.split(section.substring(1, section.length() - 1));
- int partCount = parts.length;
-
- if (partCount >= 1 && partCount <= 2) {
- String namePart = StringUtil.trimToNull(parts[0]);
- if (namePart == null) {
- throw getInvalidSectionException(section);
- }
- int namePartLength = namePart.length();
-
- parameterRestrictions = new ArrayList<>(0);
-
- if (namePart.charAt(namePartLength - 1) == ')') {
- int openParenthesisIndex = namePart.indexOf('(');
-
- if (openParenthesisIndex >= 0) {
- parameterName = namePart.substring(0, openParenthesisIndex);
-
- String[] restrictionRules = StringUtil.Patterns.COMMA_PATTERN.split(
- namePart.substring(openParenthesisIndex + 1, namePartLength - 1)
- );
-
- for (String restrictionRule : restrictionRules) {
- if (StringUtil.isEmpty(restrictionRule = StringUtil.trim(restrictionRule))) {
- continue;
- }
-
- parameterRestrictions.add(getParameterRestriction(section, restrictionRule));
- }
- } else {
- parameterName = namePart;
- }
- } else {
- parameterName = namePart;
- }
-
- if (partCount == 1) {
- allowedParameterValues = null;
- forbiddenParameterValues = null;
- } else {
- allowedParameterValues = new HashSet<>();
- forbiddenParameterValues = new HashSet<>();
-
- for (String valueRule : StringUtil.Patterns.COMMA_PATTERN.split(parts[1])) {
- if (StringUtil.isEmpty(valueRule = StringUtil.trim(valueRule))) {
- continue;
- }
-
- if (valueRule.charAt(0) == '!') {
- forbiddenParameterValues.add(valueRule.substring(1));
- } else {
- allowedParameterValues.add(valueRule);
- }
- }
-
- if (!allowedParameterValues.isEmpty()) {
- parameterRestrictions.add(new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return allowedParameterValues.contains(value);
- }
- });
- }
-
- if (!forbiddenParameterValues.isEmpty()) {
- parameterRestrictions.add(new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return !forbiddenParameterValues.contains(value);
- }
- });
- }
- }
- } else {
- throw getInvalidSectionException(section);
- }
- } else {
- parameter = false;
- value = section;
- parameterName = null;
- parameterRestrictions = null;
- allowedParameterValues = null;
- forbiddenParameterValues = null;
- }
- }
-
- public boolean isParameter() {
- return parameter;
- }
-
- public String getValue() {
- ensureValueSection("value");
- return value;
- }
-
- public String getParameterName() {
- ensureParameterSection("parameterName");
- return parameterName;
- }
-
- public List getParameterRestrictions() {
- ensureParameterSection("parameterRestrictions");
- return Collections.unmodifiableList(parameterRestrictions);
- }
-
- public boolean isSuitable(String value) {
- for (ParameterRestriction parameterRestriction : getParameterRestrictions()) {
- if (!parameterRestriction.isSuitable(value)) {
- return false;
- }
- }
-
- return true;
- }
-
- private void ensureValueSection(String fieldName) {
- if (parameter) {
- logger.error("Can't read field '" + fieldName + "' of non-value section '" + section + "'.");
- throw new IllegalStateException(String.format(
- "Can't read field '%s' of non-value section '%s'.", fieldName, section
- ));
- }
- }
-
- private void ensureParameterSection(String fieldName) {
- if (!parameter) {
- logger.error("Can't read field '" + fieldName + "' of non-parameter section '" + section + "'.");
- throw new IllegalStateException(String.format(
- "Can't read field '%s' of non-parameter section '%s'.", fieldName, section
- ));
- }
- }
-
- private static ConfigurationException getInvalidSectionException(String section) {
- return new ConfigurationException("Link section '" + section + "' has invalid format, " +
- "examples of valid formats: 'test', '{userName}', '{id(int):1,2,3}', " +
- "{title(string,!empty):!title}."
- );
- }
-
- private interface ParameterRestriction {
- boolean isSuitable(String value);
- }
-
- private static final class NegatedParameterRestriction implements ParameterRestriction {
- @Nonnull
- private final ParameterRestriction parameterRestriction;
-
- private NegatedParameterRestriction(@Nonnull ParameterRestriction parameterRestriction) {
- this.parameterRestriction = parameterRestriction;
- }
-
- @Override
- public boolean isSuitable(String value) {
- return !parameterRestriction.isSuitable(value);
- }
- }
-
- private static ParameterRestriction getParameterRestriction(String section, String restrictionRule) {
- if (restrictionRule != null && restrictionRule.startsWith("!")) {
- return new NegatedParameterRestriction(internalGetParameterRestriction(
- section, restrictionRule.substring(1)
- ));
- } else {
- return internalGetParameterRestriction(section, restrictionRule);
- }
- }
-
- @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"})
- @Nonnull
- private static ParameterRestriction internalGetParameterRestriction(String section, String restrictionRule) {
- if ("null".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return value == null;
- }
- };
- } else if ("empty".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return StringUtil.isEmpty(value);
- }
- };
- } else if ("blank".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return StringUtil.isBlank(value);
- }
- };
- } else if ("alpha".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return StringUtils.isAlpha(value);
- }
- };
- } else if ("numeric".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return StringUtils.isNumeric(value);
- }
- };
- } else if ("alphanumeric".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- return StringUtils.isAlphanumeric(value);
- }
- };
- } else if ("byte".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Byte.parseByte(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("short".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Short.parseShort(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("int".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Integer.parseInt(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("long".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Long.parseLong(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("float".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Float.parseFloat(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("double".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- Double.parseDouble(value);
- return true;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("positive".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) > 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("nonpositive".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) <= 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("negative".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) < 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("nonnegative".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) >= 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("zero".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) == 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else if ("nonzero".equalsIgnoreCase(restrictionRule)) {
- return new ParameterRestriction() {
- @Override
- public boolean isSuitable(String value) {
- try {
- return Double.parseDouble(value) != 0.0D;
- } catch (RuntimeException ignored) {
- return false;
- }
- }
- };
- } else {
- logger.error("Link section '" + section + "' contains unsupported parameter restriction '"
- + restrictionRule + "'.");
- throw new ConfigurationException(String.format(
- "Link section '%s' contains unsupported parameter restriction '%s'.",
- section, restrictionRule
- ));
- }
- }
- }
-
- /**
- * Adds interceptor to the Links. Link will be processed by interceptors before return.
- *
- * @param name name of the interceptor to add
- * @param interceptor interceptor to add
- */
- public static void addInterceptor(String name, Interceptor interceptor) {
- ensureInterceptorName(name);
-
- if (interceptor == null) {
- logger.error("Argument 'interceptor' is 'null'.");
- throw new IllegalArgumentException("Argument 'interceptor' is 'null'.");
- }
-
- interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
- try {
- if (interceptorByNameMap.containsKey(name)) {
- logger.error("Interceptor with name '" + name + "' already added.");
- throw new IllegalStateException("Interceptor with name '" + name + "' already added.");
- }
- interceptorByNameMap.put(name, interceptor);
- } finally {
- interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
- }
- }
-
- /**
- * Removes interceptor from the Links.
- *
- * @param name name of the interceptor to remove
- */
- public static void removeInterceptor(String name) {
- ensureInterceptorName(name);
-
- interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
- try {
- interceptorByNameMap.remove(name);
- } finally {
- interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
- }
- }
-
- /**
- * Checks if specified interceptor is already added to the Links.
- *
- * @param name name of the interceptor to check
- * @return {@code true} iff interceptor with specified name is added to the Links
- */
- public static boolean hasInterceptor(String name) {
- ensureInterceptorName(name);
-
- interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
- try {
- return interceptorByNameMap.containsKey(name);
- } finally {
- interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
- }
- }
-
- private static void ensureInterceptorName(String name) {
- if (name == null || name.isEmpty()) {
- logger.error("Argument 'name' is 'null' or empty.");
- throw new IllegalArgumentException("Argument 'name' is 'null' or empty.");
- }
- }
-
- private static Map getLinksByPageClass(Class extends Page> clazz) {
- Map links;
- Class parentClass = clazz;
- while ((links = linksByPage.get(parentClass)) == null && parentClass.getSuperclass() != null) {
- parentClass = parentClass.getSuperclass();
- }
- return links;
- }
-
- /**
- * Custom link processor. You can add interceptor using {@link #addInterceptor(String, Interceptor)} method.
- */
- public interface Interceptor {
- /**
- * This method will be called to postprocess link.
- *
- * @param link link to process
- * @param clazz page class
- * @param linkName {@link Link#name() name} of the link
- * @param params parameters of the link
- * @return processed link
- */
- String postprocess(String link, Class extends Page> clazz, @Nullable String linkName, Map params);
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.link;
+
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateSequenceModel;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.mutable.MutableBoolean;
+import org.nocturne.annotation.Name;
+import org.nocturne.collection.SingleEntryList;
+import org.nocturne.exception.ConfigurationException;
+import org.nocturne.exception.NocturneException;
+import org.nocturne.main.ApplicationContext;
+import org.nocturne.main.Page;
+import org.nocturne.util.StringUtil;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ *
+ * Handles link pattern methods.
+ * Each page should have @Link annotation to specify its link.
+ * It can use parameters (templates), like "profile/{handle}".
+ *
+ *
+ * If you want to redirect to SomePage, use abortWithRedirect(Links.getLink(SomePage.class)) or
+ * abortWithRedirect(SomePage.class).
+ *
+ *
+ * @author Mike Mirzayanov
+ */
+public class Links {
+ private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Links.class);
+
+ private static final Lock addLinkLock = new ReentrantLock();
+
+ private static final int INTERCEPTOR_MAX_PERMIT_COUNT = 8 * Runtime.getRuntime().availableProcessors();
+ private static final Semaphore interceptorSemaphore = new Semaphore(INTERCEPTOR_MAX_PERMIT_COUNT);
+ private static final Map interceptorByNameMap = new LinkedHashMap<>();
+
+
+ /**
+ * Stores maps for each page class. Each map contains single patterns as keys
+ * and Link instances as values.
+ */
+ private static final ConcurrentMap, Map> linksByPage = new ConcurrentHashMap<>();
+
+ /**
+ * Stores page classes by their names.
+ */
+ private static final ConcurrentMap> classesByName = new ConcurrentHashMap<>();
+
+ /**
+ * Stores link sections by links.
+ */
+ private static final ConcurrentMap> sectionsByLinkText = new ConcurrentHashMap<>();
+
+ private static List getLinksViaReflection(Class extends Page> clazz) {
+ List result = new ArrayList<>();
+ Link link = clazz.getAnnotation(Link.class);
+ if (link != null) {
+ result.add(link);
+ }
+ LinkSet linkSet = clazz.getAnnotation(LinkSet.class);
+ if (linkSet != null) {
+ result.addAll(Arrays.asList(linkSet.value()));
+ }
+ return result;
+ }
+
+ /**
+ * Use the method to get page link name. Do not use page.getClass().getSimpleName() because of two reasons:
+ * - page can have @Name annotation,
+ * - page can be actually inherited from expected ConcretePage class because of IoC.
+ *
+ * @param page Page instance.
+ * @return Page link name.
+ */
+ public static String getLinkName(@Nonnull Page page) {
+ return getLinkName(page.getClass());
+ }
+
+ /**
+ * Use the method to get page class link name. Do not use pageClass.getSimpleName() because of two reasons:
+ * - page class can have @Name annotation,
+ * - page class can be actually inherited from expected ConcretePage class because of IoC.
+ *
+ * @param pageClass Page class.
+ * @return Page link name.
+ */
+ public static String getLinkName(@Nonnull Class extends Page> pageClass) {
+ Class> clazz = pageClass;
+
+ while (clazz != null && clazz.getAnnotation(Link.class) == null && clazz.getAnnotation(LinkSet.class) == null) {
+ clazz = clazz.getSuperclass();
+ }
+
+ if (clazz == null) {
+ logger.error("Page class should have @Link or @LinkSet annotation, but "
+ + pageClass.getName() + " hasn't.");
+ throw new NocturneException("Page class should have @Link or @LinkSet annotation, but "
+ + pageClass.getName() + " hasn't.");
+ }
+
+ Name name = clazz.getAnnotation(Name.class);
+ if (name == null) {
+ return clazz.getSimpleName();
+ } else {
+ return name.value();
+ }
+ }
+
+ /**
+ * @param clazz Page class to be added into Links.
+ * After it you can get it's link via getLink, or using @link directive
+ * from template. Link may contain template sections, like "profile/{handle}".
+ * @param linkSet List of links to be added for class {@code clazz}.
+ */
+ public static void add(Class extends Page> clazz, List linkSet) {
+ addLinkLock.lock();
+
+ try {
+ String name = getLinkName(clazz);
+ if (classesByName.containsKey(name) && !clazz.equals(classesByName.get(name))) {
+ logger.error("Can't add page which is not unique by it's name: " + clazz.getName() + '.');
+ throw new ConfigurationException("Can't add page which is not unique by it's name: "
+ + clazz.getName() + '.');
+ }
+ classesByName.put(name, clazz);
+
+ Map links = getLinksByPageClass(clazz);
+ if (links == null) {
+ // It is important that used synchronizedMap, because of "synchronized(links) {..}" later in code.
+ links = Collections.synchronizedMap(new LinkedHashMap());
+ }
+
+ for (Link link : linkSet) {
+ String[] pageLinks = StringUtil.Patterns.SEMICOLON_PATTERN.split(link.value());
+ for (String pageLink : pageLinks) {
+ if (!sectionsByLinkText.containsKey(pageLink)) {
+ sectionsByLinkText.putIfAbsent(pageLink, parseLinkToLinkSections(pageLink));
+ }
+
+ for (Map linkMap : linksByPage.values()) {
+ if (linkMap.containsKey(pageLink)) {
+ logger.error("Page link \"" + pageLink + "\" already registered.");
+ throw new ConfigurationException("Page link \"" + pageLink + "\" already registered.");
+ }
+ }
+ if (links.containsKey(pageLink)) {
+ logger.error("Page link \"" + pageLink + "\" already registered.");
+ throw new ConfigurationException("Page link \"" + pageLink + "\" already registered.");
+ }
+
+ links.put(pageLink, link);
+ }
+ }
+
+ linksByPage.put(clazz, links);
+ } finally {
+ addLinkLock.unlock();
+ }
+ }
+
+ /**
+ * @param clazz Page class to be added into Links.
+ * After it you can get it's link via getLink, or using @link directive
+ * from template. Link may contain template sections, like "profile/{handle}".
+ */
+ public static void add(Class extends Page> clazz) {
+ List linkSet = getLinksViaReflection(clazz);
+ if (linkSet.isEmpty()) {
+ logger.error("Can't find link for page " + clazz.getName() + '.');
+ throw new ConfigurationException("Can't find link for page " + clazz.getName() + '.');
+ }
+
+ add(clazz, linkSet);
+ }
+
+ /**
+ * @param clazz Page class.
+ * @param linkName desired {@link Link#name() name} of the link
+ * @param params parameters for substitution (for example link "profile/{handle}"
+ * may use "handle" key in the map.
+ * @return link for page. If there many links for page, returns one of them, which matches better
+ * @throws NoSuchLinkException if no such link exists
+ */
+ @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"})
+ public static String getLinkByMap(Class extends Page> clazz, @Nullable String linkName, Map params) {
+ MutableBoolean multiValueParams = new MutableBoolean();
+ Map> nonEmptyParams = getNonEmptyParams(params, multiValueParams);
+
+ int bestMatchedCount = -1;
+ List bestMatchedLinkSections = null;
+ Link bestMatchedLink = null;
+
+ for (Map.Entry entry : getLinksByPageClass(clazz).entrySet()) {
+ if (linkName != null && !linkName.isEmpty() && !linkName.equals(entry.getValue().name())) {
+ continue;
+ }
+
+ List sections = sectionsByLinkText.get(entry.getKey());
+ boolean matched = true;
+ int matchedCount = 0;
+ for (LinkSection section : sections) {
+ if (section.isParameter()) {
+ ++matchedCount;
+ List values = nonEmptyParams.get(section.getParameterName());
+ String value = values == null ? null : values.get(0);
+ if (value == null || !section.isSuitable(value)) {
+ matched = false;
+ break;
+ }
+ }
+ }
+ if (matched && matchedCount > bestMatchedCount) {
+ bestMatchedCount = matchedCount;
+ bestMatchedLinkSections = sections;
+ bestMatchedLink = entry.getValue();
+ }
+ }
+
+ if (bestMatchedLinkSections == null) {
+ if (linkName == null || linkName.isEmpty()) {
+ throw new NoSuchLinkException("Can't find link for page " + clazz.getName() + '.');
+ } else {
+ throw new NoSuchLinkException("Can't find link with name '"
+ + linkName + "' for page " + clazz.getName() + '.');
+ }
+ }
+
+ StringBuilder result = new StringBuilder(ApplicationContext.getInstance().getContextPath());
+ Set usedKeys = new HashSet<>();
+
+ for (LinkSection section : bestMatchedLinkSections) {
+ String item;
+
+ if (section.isParameter()) {
+ usedKeys.add(section.getParameterName());
+ item = nonEmptyParams.get(section.getParameterName()).get(0);
+ } else {
+ item = section.getValue();
+ }
+
+ result.append('/').append(item);
+ }
+
+ if (nonEmptyParams.size() > usedKeys.size() || multiValueParams.isTrue()) {
+ boolean first = true;
+ for (Map.Entry> entry : nonEmptyParams.entrySet()) {
+ List values = entry.getValue();
+ int valueCount = values.size();
+ int startIndex = valueCount;
+
+ if (usedKeys.contains(entry.getKey())) {
+ if (valueCount > 1) {
+ startIndex = 1;
+ }
+ } else {
+ startIndex = 0;
+ }
+
+ for (int i = startIndex; i < valueCount; ++i) {
+ if (first) {
+ result.append('?');
+ first = false;
+ } else {
+ result.append('&');
+ }
+
+ try {
+ result.append(entry.getKey()).append('=').append(URLEncoder.encode(values.get(i), StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException e) {
+ // No operations.
+ }
+ }
+ }
+ }
+
+ String linkResult = result.toString();
+
+ interceptorSemaphore.acquireUninterruptibly();
+ try {
+ for (Map.Entry e : interceptorByNameMap.entrySet()) {
+ boolean skip = false;
+ for (String skipInterceptor : bestMatchedLink.skipInterceptors()) {
+ if (skipInterceptor.equals(e.getKey())) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ linkResult = e.getValue().postprocess(linkResult, clazz, linkName, params);
+ }
+ }
+ } finally {
+ interceptorSemaphore.release();
+ }
+
+ return linkResult;
+ }
+
+ private static Map> getNonEmptyParams(Map params, MutableBoolean multiValueParams) {
+ multiValueParams.setValue(false);
+ Map> nonEmptyParams = new LinkedHashMap<>();
+
+ for (Map.Entry entry : params.entrySet()) {
+ Object value = entry.getValue();
+ if (!isMissingValue(value)) {
+ List list = toStringList(value);
+ int count = list.size();
+
+ if (count > 0) {
+ nonEmptyParams.put(entry.getKey(), list);
+
+ if (count > 1) {
+ multiValueParams.setValue(true);
+ }
+ }
+ }
+ }
+
+ return nonEmptyParams;
+ }
+
+ @Nonnull
+ private static List toStringList(@Nonnull Object value) {
+ if (value instanceof TemplateSequenceModel) {
+ return toStringList((TemplateSequenceModel) value);
+ } else if (value instanceof Collection) {
+ return toStringList((Collection) value);
+ } else if (value.getClass().isArray()) {
+ int count = Array.getLength(value);
+ List list = new ArrayList<>(count);
+
+ for (int i = 0; i < count; ++i) {
+ Object item = Array.get(value, i);
+ if (item != null) {
+ list.add(item.toString());
+ }
+ }
+
+ return list;
+ } else {
+ return new SingleEntryList<>(value.toString());
+ }
+ }
+
+ private static List toStringList(@Nonnull TemplateSequenceModel sequence) {
+ int count = getSize(sequence);
+ List list = new ArrayList<>(count);
+
+ for (int i = 0; i < count; ++i) {
+ TemplateModel item;
+ try {
+ item = sequence.get(i);
+ } catch (TemplateModelException e) {
+ logger.error("Can't get item of Freemarker sequence.", e);
+ throw new NocturneException("Can't get item of Freemarker sequence.", e);
+ }
+
+ if (item != null) {
+ list.add(item.toString());
+ }
+ }
+
+ return list;
+ }
+
+ private static List toStringList(@Nonnull Collection collection) {
+ List list = new ArrayList<>(collection.size());
+
+ for (Object item : collection) {
+ if (item != null) {
+ list.add(item.toString());
+ }
+ }
+
+ return list;
+ }
+
+ private static int getSize(@Nonnull TemplateSequenceModel sequence) {
+ try {
+ return sequence.size();
+ } catch (TemplateModelException e) {
+ logger.error("Can't get size of Freemarker sequence.", e);
+ throw new NocturneException("Can't get size of Freemarker sequence.", e);
+ }
+ }
+
+ /**
+ * @param clazz Page class.
+ * @param params parameters for substitution (for example link "profile/{handle}"
+ * may use "handle" key in the map.
+ * @return Returns link for page. If there many links for page, returns
+ * one of them, which matches better. Throws NoSuchLinkException
+ * if no such link exists.
+ */
+ public static String getLinkByMap(Class extends Page> clazz, Map params) {
+ return getLinkByMap(clazz, null, params);
+ }
+
+ /**
+ * @param name Page name.
+ * @param linkName desired {@link Link#name() name} of the link
+ * @param params parameters for substitution (for example link "profile/{handle}"
+ * may use "handle" key in the map.
+ * @return Returns link for page. If there many links for page, returns
+ * one of them, which matches better. Throws NoSuchLinkException
+ * if no such link exists.
+ */
+ public static String getLinkByMap(String name, @Nullable String linkName, Map params) {
+ Class extends Page> clazz = classesByName.get(name);
+
+ if (clazz == null) {
+ logger.error("Can't find link for page " + name + '.');
+ throw new NoSuchLinkException("Can't find link for page " + name + '.');
+ } else {
+ return getLinkByMap(clazz, linkName, params);
+ }
+ }
+
+ /**
+ * @param name Page name.
+ * @param params parameters for substitution (for example link "profile/{handle}"
+ * may use "handle" key in the map.
+ * @return Returns link for page. If there many links for page, returns
+ * one of them, which matches better. Throws NoSuchLinkException
+ * if no such link exists.
+ */
+ public static String getLinkByMap(String name, Map params) {
+ return getLinkByMap(name, null, params);
+ }
+
+ private static boolean isMissingValue(Object value) {
+ if (value == null) {
+ return true;
+ }
+
+ if (value instanceof TemplateSequenceModel) {
+ return getSize((TemplateSequenceModel) value) <= 0;
+ } else if (value instanceof Collection) {
+ return ((Collection) value).isEmpty();
+ } else if (value.getClass().isArray()) {
+ return Array.getLength(value) <= 0;
+ } else {
+ return value.toString().isEmpty();
+ }
+ }
+
+ /**
+ * @param params Array of values.
+ * @return Correspondent map.
+ */
+ private static Map convertArrayToMap(Object... params) {
+ int paramCount = params.length;
+
+ if (paramCount == 0) {
+ return Collections.emptyMap();
+ }
+
+ if (paramCount % 2 != 0) {
+ logger.error("Params should contain even number of elements.");
+ throw new IllegalArgumentException("Params should contain even number of elements.");
+ }
+
+ Map map = new LinkedHashMap<>();
+
+ for (int paramIndex = 0; paramIndex < paramCount; paramIndex += 2) {
+ map.put(params[paramIndex].toString(), params[paramIndex + 1]);
+ }
+
+ return map;
+ }
+
+ /**
+ * @param pageClass Page class.
+ * @return link for page. If there many links for page, returns one of them, which matches better
+ * @throws NoSuchLinkException if no such link exists
+ */
+ public static String getLink(Class extends Page> pageClass) {
+ return getLinkByMap(pageClass, null, Collections.emptyMap());
+ }
+
+ /**
+ * @param pageClass Page class.
+ * @param params Even length sequence of Objects. Even elements mean keys and odd
+ * values of parameters map. For example ["handle", "MikeMirzayanov", "topic", 123]
+ * means map {@literal ["handle" => "MikeMirzayanov", "topic" => 123]}. Method skips params with null value.
+ * @return link for page. If there many links for page, returns one of them, which matches better
+ * @throws NoSuchLinkException if no such link exists
+ */
+ public static String getLink(Class extends Page> pageClass, Object... params) {
+ return getLinkByMap(pageClass, null, convertArrayToMap(params));
+ }
+
+ /**
+ * @param name Page name.
+ * @param params Even length sequence of Objects. Even elements mean keys and odd
+ * values of parameters map. For example ["handle", "MikeMirzayanov", "topic", 123]
+ * means map {@literal ["handle" => "MikeMirzayanov", "topic" => 123]}. Method skips params with null value.
+ * @return link for page. If there many links for page, returns one of them, which matches better
+ * @throws NoSuchLinkException if no such link exists
+ */
+ public static String getLink(String name, Object... params) {
+ return getLinkByMap(name, null, convertArrayToMap(params));
+ }
+
+ /**
+ * @param link Relative link to the page started from "/".
+ * For example, "/profile/MikeMirzayanov".
+ * @return Result instance or {@code null} if not found.
+ */
+ public static LinkMatchResult match(String link) {
+ // Remove anchor.
+ if (link.contains("#")) {
+ link = link.substring(0, link.lastIndexOf('#'));
+ }
+
+ // Remove query string.
+ if (link.contains("?")) {
+ link = link.substring(0, link.lastIndexOf('?'));
+ }
+
+ if (!link.startsWith("/")) {
+ logger.error("Link \"" + link + "\" doesn't start with '/'.");
+ throw new IllegalArgumentException("Link \"" + link + "\" doesn't start with '/'.");
+ }
+
+ String[] linkTokens = StringUtil.Patterns.SLASH_PATTERN.split(link.substring(1));
+
+ for (Map.Entry, Map> listEntry : linksByPage.entrySet()) {
+ Map patterns = listEntry.getValue();
+ if (patterns == null) {
+ continue;
+ }
+
+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (patterns) {
+ for (Map.Entry patternEntry : patterns.entrySet()) {
+ String linkText = patternEntry.getKey();
+ Map attrs = match(linkTokens, linkText);
+
+ if (attrs != null) {
+ return new LinkMatchResult(listEntry.getKey(), linkText, attrs, patternEntry.getValue());
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param linkTokens For example, ["profile", "MikeMirzayanov"] for requested link "/profile/MikeMirzayanov".
+ * @param linkText Link pattern, like "profile/{handle}".
+ * @return Correspondent params map or {@code null} if not matched.
+ */
+ private static Map match(String[] linkTokens, String linkText) {
+ List sections = sectionsByLinkText.get(linkText);
+ if (sections == null) {
+ logger.error("Can't find sections for linkText=\"" + linkText + "\".");
+ throw new NocturneException("Can't find sections for linkText=\"" + linkText + "\".");
+ }
+
+ int linkTokenCount = linkTokens.length;
+
+ if (linkTokenCount == sections.size()) {
+ Map attrs = new HashMap<>();
+
+ for (int linkTokenIndex = 0; linkTokenIndex < linkTokenCount; ++linkTokenIndex) {
+ LinkSection section = sections.get(linkTokenIndex);
+
+ if (section.isParameter()) {
+ if (!section.isSuitable(linkTokens[linkTokenIndex])) {
+ return null;
+ }
+ attrs.put(section.getParameterName(), linkTokens[linkTokenIndex]);
+ } else {
+ if (!section.getValue().equals(linkTokens[linkTokenIndex])) {
+ return null;
+ }
+ }
+ }
+
+ return attrs;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * If case of getLink-like methods if they can't find requested link.
+ */
+ @SuppressWarnings({"DeserializableClassInSecureContext", "UncheckedExceptionClass"})
+ public static class NoSuchLinkException extends RuntimeException {
+ /**
+ * @param message Error message.
+ */
+ public NoSuchLinkException(String message) {
+ super(message);
+ }
+ }
+
+ private static List parseLinkToLinkSections(String linkText) {
+ if (linkText == null || linkText.startsWith("/") || linkText.endsWith("/")) {
+ logger.error("Page link has illegal format, use links like 'home', 'page/{index}', " +
+ "'page/{index(long,positive):1,2,3}', 'section/{name(string,!blank):!a,b,c}'.");
+ throw new ConfigurationException("Page link has illegal format, use links like 'home', 'page/{index}', " +
+ "'page/{index(long,positive):1,2,3}', 'section/{name(string,!blank):!a,b,c}'."
+ );
+ }
+
+ String[] sections = StringUtil.Patterns.SLASH_PATTERN.split(linkText);
+ List linkSections = new ArrayList<>(sections.length);
+ for (String section : sections) {
+ linkSections.add(new LinkSection(section));
+ }
+
+ return linkSections;
+ }
+
+ private static final class LinkSection {
+ private final String section;
+ private final boolean parameter;
+ private final String value;
+ private final String parameterName;
+ private final List parameterRestrictions;
+ private final Set allowedParameterValues;
+ private final Set forbiddenParameterValues;
+
+ /**
+ * @param section Each part of link, i.e. "home" from link "test/home"
+ */
+ @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod", "OverlyNestedMethod"})
+ private LinkSection(String section) {
+ section = StringUtil.trim(section);
+ this.section = section;
+
+ if (section.startsWith("{") && section.endsWith("}")) {
+ parameter = true;
+ value = null;
+
+ String[] parts = StringUtil.Patterns.COLON_PATTERN.split(section.substring(1, section.length() - 1));
+ int partCount = parts.length;
+
+ if (partCount >= 1 && partCount <= 2) {
+ String namePart = StringUtil.trimToNull(parts[0]);
+ if (namePart == null) {
+ throw getInvalidSectionException(section);
+ }
+ int namePartLength = namePart.length();
+
+ parameterRestrictions = new ArrayList<>(0);
+
+ if (namePart.charAt(namePartLength - 1) == ')') {
+ int openParenthesisIndex = namePart.indexOf('(');
+
+ if (openParenthesisIndex >= 0) {
+ parameterName = namePart.substring(0, openParenthesisIndex);
+
+ String[] restrictionRules = StringUtil.Patterns.COMMA_PATTERN.split(
+ namePart.substring(openParenthesisIndex + 1, namePartLength - 1)
+ );
+
+ for (String restrictionRule : restrictionRules) {
+ if (StringUtil.isEmpty(restrictionRule = StringUtil.trim(restrictionRule))) {
+ continue;
+ }
+
+ parameterRestrictions.add(getParameterRestriction(section, restrictionRule));
+ }
+ } else {
+ parameterName = namePart;
+ }
+ } else {
+ parameterName = namePart;
+ }
+
+ if (partCount == 1) {
+ allowedParameterValues = null;
+ forbiddenParameterValues = null;
+ } else {
+ allowedParameterValues = new HashSet<>();
+ forbiddenParameterValues = new HashSet<>();
+
+ for (String valueRule : StringUtil.Patterns.COMMA_PATTERN.split(parts[1])) {
+ if (StringUtil.isEmpty(valueRule = StringUtil.trim(valueRule))) {
+ continue;
+ }
+
+ if (valueRule.charAt(0) == '!') {
+ forbiddenParameterValues.add(valueRule.substring(1));
+ } else {
+ allowedParameterValues.add(valueRule);
+ }
+ }
+
+ if (!allowedParameterValues.isEmpty()) {
+ parameterRestrictions.add(new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return allowedParameterValues.contains(value);
+ }
+ });
+ }
+
+ if (!forbiddenParameterValues.isEmpty()) {
+ parameterRestrictions.add(new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return !forbiddenParameterValues.contains(value);
+ }
+ });
+ }
+ }
+ } else {
+ throw getInvalidSectionException(section);
+ }
+ } else {
+ parameter = false;
+ value = section;
+ parameterName = null;
+ parameterRestrictions = null;
+ allowedParameterValues = null;
+ forbiddenParameterValues = null;
+ }
+ }
+
+ public boolean isParameter() {
+ return parameter;
+ }
+
+ public String getValue() {
+ ensureValueSection("value");
+ return value;
+ }
+
+ public String getParameterName() {
+ ensureParameterSection("parameterName");
+ return parameterName;
+ }
+
+ public List getParameterRestrictions() {
+ ensureParameterSection("parameterRestrictions");
+ return Collections.unmodifiableList(parameterRestrictions);
+ }
+
+ public boolean isSuitable(String value) {
+ for (ParameterRestriction parameterRestriction : getParameterRestrictions()) {
+ if (!parameterRestriction.isSuitable(value)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void ensureValueSection(String fieldName) {
+ if (parameter) {
+ logger.error("Can't read field '" + fieldName + "' of non-value section '" + section + "'.");
+ throw new IllegalStateException(String.format(
+ "Can't read field '%s' of non-value section '%s'.", fieldName, section
+ ));
+ }
+ }
+
+ private void ensureParameterSection(String fieldName) {
+ if (!parameter) {
+ logger.error("Can't read field '" + fieldName + "' of non-parameter section '" + section + "'.");
+ throw new IllegalStateException(String.format(
+ "Can't read field '%s' of non-parameter section '%s'.", fieldName, section
+ ));
+ }
+ }
+
+ private static ConfigurationException getInvalidSectionException(String section) {
+ return new ConfigurationException("Link section '" + section + "' has invalid format, " +
+ "examples of valid formats: 'test', '{userName}', '{id(int):1,2,3}', " +
+ "{title(string,!empty):!title}."
+ );
+ }
+
+ private interface ParameterRestriction {
+ boolean isSuitable(String value);
+ }
+
+ private static final class NegatedParameterRestriction implements ParameterRestriction {
+ @Nonnull
+ private final ParameterRestriction parameterRestriction;
+
+ private NegatedParameterRestriction(@Nonnull ParameterRestriction parameterRestriction) {
+ this.parameterRestriction = parameterRestriction;
+ }
+
+ @Override
+ public boolean isSuitable(String value) {
+ return !parameterRestriction.isSuitable(value);
+ }
+ }
+
+ private static ParameterRestriction getParameterRestriction(String section, String restrictionRule) {
+ if (restrictionRule != null && restrictionRule.startsWith("!")) {
+ return new NegatedParameterRestriction(internalGetParameterRestriction(
+ section, restrictionRule.substring(1)
+ ));
+ } else {
+ return internalGetParameterRestriction(section, restrictionRule);
+ }
+ }
+
+ @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"})
+ @Nonnull
+ private static ParameterRestriction internalGetParameterRestriction(String section, String restrictionRule) {
+ if ("null".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return value == null;
+ }
+ };
+ } else if ("empty".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return StringUtil.isEmpty(value);
+ }
+ };
+ } else if ("blank".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return StringUtil.isBlank(value);
+ }
+ };
+ } else if ("alpha".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return StringUtils.isAlpha(value);
+ }
+ };
+ } else if ("numeric".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return StringUtils.isNumeric(value);
+ }
+ };
+ } else if ("alphanumeric".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ return StringUtils.isAlphanumeric(value);
+ }
+ };
+ } else if ("byte".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Byte.parseByte(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("short".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Short.parseShort(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("int".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Integer.parseInt(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("long".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Long.parseLong(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("float".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Float.parseFloat(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("double".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ Double.parseDouble(value);
+ return true;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("positive".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) > 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("nonpositive".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) <= 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("negative".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) < 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("nonnegative".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) >= 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("zero".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) == 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else if ("nonzero".equalsIgnoreCase(restrictionRule)) {
+ return new ParameterRestriction() {
+ @Override
+ public boolean isSuitable(String value) {
+ try {
+ return Double.parseDouble(value) != 0.0D;
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+ };
+ } else {
+ logger.error("Link section '" + section + "' contains unsupported parameter restriction '"
+ + restrictionRule + "'.");
+ throw new ConfigurationException(String.format(
+ "Link section '%s' contains unsupported parameter restriction '%s'.",
+ section, restrictionRule
+ ));
+ }
+ }
+ }
+
+ /**
+ * Adds interceptor to the Links. Link will be processed by interceptors before return.
+ *
+ * @param name name of the interceptor to add
+ * @param interceptor interceptor to add
+ */
+ public static void addInterceptor(String name, Interceptor interceptor) {
+ ensureInterceptorName(name);
+
+ if (interceptor == null) {
+ logger.error("Argument 'interceptor' is 'null'.");
+ throw new IllegalArgumentException("Argument 'interceptor' is 'null'.");
+ }
+
+ interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
+ try {
+ if (interceptorByNameMap.containsKey(name)) {
+ logger.error("Interceptor with name '" + name + "' already added.");
+ throw new IllegalStateException("Interceptor with name '" + name + "' already added.");
+ }
+ interceptorByNameMap.put(name, interceptor);
+ } finally {
+ interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
+ }
+ }
+
+ /**
+ * Removes interceptor from the Links.
+ *
+ * @param name name of the interceptor to remove
+ */
+ public static void removeInterceptor(String name) {
+ ensureInterceptorName(name);
+
+ interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
+ try {
+ interceptorByNameMap.remove(name);
+ } finally {
+ interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
+ }
+ }
+
+ /**
+ * Checks if specified interceptor is already added to the Links.
+ *
+ * @param name name of the interceptor to check
+ * @return {@code true} iff interceptor with specified name is added to the Links
+ */
+ public static boolean hasInterceptor(String name) {
+ ensureInterceptorName(name);
+
+ interceptorSemaphore.acquireUninterruptibly(INTERCEPTOR_MAX_PERMIT_COUNT);
+ try {
+ return interceptorByNameMap.containsKey(name);
+ } finally {
+ interceptorSemaphore.release(INTERCEPTOR_MAX_PERMIT_COUNT);
+ }
+ }
+
+ private static void ensureInterceptorName(String name) {
+ if (name == null || name.isEmpty()) {
+ logger.error("Argument 'name' is 'null' or empty.");
+ throw new IllegalArgumentException("Argument 'name' is 'null' or empty.");
+ }
+ }
+
+ private static Map getLinksByPageClass(Class extends Page> clazz) {
+ Map links;
+ Class parentClass = clazz;
+ while ((links = linksByPage.get(parentClass)) == null && parentClass.getSuperclass() != null) {
+ parentClass = parentClass.getSuperclass();
+ }
+ return links;
+ }
+
+ /**
+ * Custom link processor. You can add interceptor using {@link #addInterceptor(String, Interceptor)} method.
+ */
+ public interface Interceptor {
+ /**
+ * This method will be called to postprocess link.
+ *
+ * @param link link to process
+ * @param clazz page class
+ * @param linkName {@link Link#name() name} of the link
+ * @param params parameters of the link
+ * @return processed link
+ */
+ String postprocess(String link, Class extends Page> clazz, @Nullable String linkName, Map params);
+ }
+}
diff --git a/code/src/main/java/org/nocturne/listener/PageRequestListener.java b/code/src/main/java/org/nocturne/listener/PageRequestListener.java
index 297c072..3952b13 100644
--- a/code/src/main/java/org/nocturne/listener/PageRequestListener.java
+++ b/code/src/main/java/org/nocturne/listener/PageRequestListener.java
@@ -1,32 +1,32 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.listener;
-
-import org.nocturne.main.Page;
-
-/**
- * You can implement this interface to handle
- * requests to application pages.
- *
- * @author Mike Mirzayanov
- */
-public interface PageRequestListener {
- /**
- * Will be called before processing specified page. Just
- * after request routing.
- *
- * @param page Page to be processed.
- */
- void beforeProcessPage(Page page);
-
- /**
- * Will be called after request processed the page.
- * It doesn't matter if page fails with exception - this
- * method will be executed.
- *
- * @param page Processed page.
- * @param t {@code null} if no throwable has been thrown.
- */
- void afterProcessPage(Page page, Throwable t);
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.listener;
+
+import org.nocturne.main.Page;
+
+/**
+ * You can implement this interface to handle
+ * requests to application pages.
+ *
+ * @author Mike Mirzayanov
+ */
+public interface PageRequestListener {
+ /**
+ * Will be called before processing specified page. Just
+ * after request routing.
+ *
+ * @param page Page to be processed.
+ */
+ void beforeProcessPage(Page page);
+
+ /**
+ * Will be called after request processed the page.
+ * It doesn't matter if page fails with exception - this
+ * method will be executed.
+ *
+ * @param page Processed page.
+ * @param t {@code null} if no throwable has been thrown.
+ */
+ void afterProcessPage(Page page, Throwable t);
+}
diff --git a/code/src/main/java/org/nocturne/main/ActionMap.java b/code/src/main/java/org/nocturne/main/ActionMap.java
index 448552e..5d9837e 100644
--- a/code/src/main/java/org/nocturne/main/ActionMap.java
+++ b/code/src/main/java/org/nocturne/main/ActionMap.java
@@ -1,229 +1,229 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.main;
-
-import net.sf.cglib.reflect.FastClass;
-import net.sf.cglib.reflect.FastMethod;
-import org.nocturne.annotation.Action;
-import org.nocturne.annotation.Invalid;
-import org.nocturne.annotation.Parameter;
-import org.nocturne.annotation.Validate;
-import org.nocturne.exception.ConfigurationException;
-import org.nocturne.exception.NocturneException;
-import org.nocturne.util.StringUtil;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Stores information about magic methods in the component.
- *
- * @author Mike Mirzayanov
- */
-class ActionMap {
- private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ActionMap.class);
-
- /* Default action has empty key "". */
- private final Map actions = new ConcurrentHashMap<>();
-
- /* Default validator has empty key "". */
- private final Map validators = new ConcurrentHashMap<>();
-
- /* Default invalid method has empty key "". */
- private final Map invalids = new ConcurrentHashMap<>();
-
- ActionMap(Class extends Component> pageClass) {
- FastClass clazz = FastClass.create(pageClass);
-
- List methods = new ArrayList<>();
- Class> auxClass = pageClass;
- while (auxClass != null) {
- methods.addAll(Arrays.asList(auxClass.getDeclaredMethods()));
- auxClass = auxClass.getSuperclass();
- }
-
- for (Method method : methods) {
- processMethod(clazz, method);
- }
-
- for (Method method : methods) {
- processMethodAsDefault(clazz, method);
- }
- }
-
- private void processMethodAsDefault(FastClass clazz, Method method) {
- if (!actions.containsKey("") && "action".equals(method.getName()) && method.getParameterTypes().length == 0) {
- if (method.getReturnType() != void.class) {
- logger.error("Default action method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- throw new ConfigurationException("Default action method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- }
- actions.put("", new ActionMethod(clazz.getMethod(method), method.getAnnotation(Action.class)));
- }
-
- if (!validators.containsKey("") && "validate".equals(method.getName()) && method.getParameterTypes().length == 0) {
- if (method.getReturnType() != boolean.class) {
- logger.error("Default validation method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return boolean.");
- throw new ConfigurationException("Default validation method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return boolean.");
- }
- validators.put("", clazz.getMethod(method));
- }
-
- if (!invalids.containsKey("") && "invalid".equals(method.getName()) && method.getParameterTypes().length == 0) {
- if (method.getReturnType() != void.class) {
- logger.error("Default invalid method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- throw new ConfigurationException("Default invalid method [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- }
- invalids.put("", clazz.getMethod(method));
- }
- }
-
- private static void ensureProperlyAnnotatedParameters(Method method) {
- if (method.getParameterTypes().length != method.getParameterAnnotations().length) {
- logger.error("Expected \"method.getParameterTypes().length != method.getParameterAnnotations().length\".");
- throw new NocturneException("Expected \"method.getParameterTypes().length != method.getParameterAnnotations().length\".");
- }
-
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- for (Annotation[] annotations : parameterAnnotations) {
- boolean hasParameter = false;
- boolean hasNamedParameter = false;
- for (Annotation annotation : annotations) {
- if (annotation instanceof Parameter) {
- hasParameter = true;
- hasNamedParameter = !StringUtil.isEmpty(((Parameter) annotation).name());
- }
- }
- if (!hasParameter) {
- logger.error("Each parameter of the method " + method.getDeclaringClass().getName()
- + '#' + method.getName() + " should be annotated with @Parameter.");
- throw new ConfigurationException("Each parameter of the method " + method.getDeclaringClass().getName()
- + '#' + method.getName() + " should be annotated with @Parameter.");
- }
- if (!hasNamedParameter) {
- logger.error("Each @Parameter in the method " + method.getDeclaringClass().getName()
- + '#' + method.getName() + " should have name.");
- throw new ConfigurationException("Each @Parameter in the method " + method.getDeclaringClass().getName()
- + '#' + method.getName() + " should have name.");
- }
- }
- }
-
- private void processMethod(FastClass clazz, Method method) {
- Action action = method.getAnnotation(Action.class);
-
- if (action != null) {
- if (actions.containsKey(action.value())) {
- logger.error("There are two or more methods for " +
- clazz.getName() + " marked with @Action[" + action.value() + "].");
- throw new ConfigurationException("There are two or more methods for " +
- clazz.getName() + " marked with @Action[" + action.value() + "].");
- }
-
- ensureProperlyAnnotatedParameters(method);
-
- if (method.getReturnType() != void.class) {
- logger.error("Method with annotation @Action [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- throw new ConfigurationException("Method with annotation @Action [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- }
-
- actions.put(action.value(), new ActionMethod(clazz.getMethod(method), action));
- }
-
- Validate validate = method.getAnnotation(Validate.class);
-
- if (validate != null) {
- if (validators.containsKey(validate.value())) {
- logger.error("There are two or more methods for " +
- clazz.getName() + " marked with @Validate[" + validate.value() + "].");
- throw new ConfigurationException("There are two or more methods for " +
- clazz.getName() + " marked with @Validate[" + validate.value() + "].");
- }
-
- ensureProperlyAnnotatedParameters(method);
-
- if (method.getReturnType() != boolean.class) {
- logger.error("Method with annotation @Validate [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return boolean.");
- throw new ConfigurationException("Method with annotation @Validate [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return boolean.");
- }
-
- validators.put(validate.value(), clazz.getMethod(method));
- }
-
- Invalid invalid = method.getAnnotation(Invalid.class);
-
- if (invalid != null) {
- if (invalids.containsKey(invalid.value())) {
- logger.error("There are two or more methods for " +
- clazz.getName() + " marked with @Invalid[" + invalid.value() + "].");
- throw new ConfigurationException("There are two or more methods for " +
- clazz.getName() + " marked with @Invalid[" + invalid.value() + "].");
- }
-
- ensureProperlyAnnotatedParameters(method);
-
- if (method.getReturnType() != void.class) {
- logger.error("Method with annotation @Invalid [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- throw new ConfigurationException("Method with annotation @Invalid [name=" + method.getName() + ", " +
- "class=" + clazz.getName() + "] should return void.");
- }
-
- invalids.put(invalid.value(), clazz.getMethod(method));
- }
- }
-
- ActionMethod getActionMethod(String action) {
- if (actions.containsKey(action)) {
- return actions.get(action);
- } else {
- return actions.get("");
- }
- }
-
- FastMethod getValidateMethod(String action) {
- if (validators.containsKey(action)) {
- return validators.get(action);
- } else {
- return validators.get("");
- }
- }
-
- FastMethod getInvalidMethod(String action) {
- if (invalids.containsKey(action)) {
- return invalids.get(action);
- } else {
- return invalids.get("");
- }
- }
-
- public static final class ActionMethod {
- private final FastMethod method;
- private final Action action;
-
- private ActionMethod(FastMethod method, Action action) {
- this.method = method;
- this.action = action;
- }
-
- public FastMethod getMethod() {
- return method;
- }
-
- public Action getAction() {
- return action;
- }
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.main;
+
+import net.sf.cglib.reflect.FastClass;
+import net.sf.cglib.reflect.FastMethod;
+import org.nocturne.annotation.Action;
+import org.nocturne.annotation.Invalid;
+import org.nocturne.annotation.Parameter;
+import org.nocturne.annotation.Validate;
+import org.nocturne.exception.ConfigurationException;
+import org.nocturne.exception.NocturneException;
+import org.nocturne.util.StringUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Stores information about magic methods in the component.
+ *
+ * @author Mike Mirzayanov
+ */
+class ActionMap {
+ private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ActionMap.class);
+
+ /* Default action has empty key "". */
+ private final Map actions = new ConcurrentHashMap<>();
+
+ /* Default validator has empty key "". */
+ private final Map validators = new ConcurrentHashMap<>();
+
+ /* Default invalid method has empty key "". */
+ private final Map invalids = new ConcurrentHashMap<>();
+
+ ActionMap(Class extends Component> pageClass) {
+ FastClass clazz = FastClass.create(pageClass);
+
+ List methods = new ArrayList<>();
+ Class> auxClass = pageClass;
+ while (auxClass != null) {
+ methods.addAll(Arrays.asList(auxClass.getDeclaredMethods()));
+ auxClass = auxClass.getSuperclass();
+ }
+
+ for (Method method : methods) {
+ processMethod(clazz, method);
+ }
+
+ for (Method method : methods) {
+ processMethodAsDefault(clazz, method);
+ }
+ }
+
+ private void processMethodAsDefault(FastClass clazz, Method method) {
+ if (!actions.containsKey("") && "action".equals(method.getName()) && method.getParameterTypes().length == 0) {
+ if (method.getReturnType() != void.class) {
+ logger.error("Default action method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ throw new ConfigurationException("Default action method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ }
+ actions.put("", new ActionMethod(clazz.getMethod(method), method.getAnnotation(Action.class)));
+ }
+
+ if (!validators.containsKey("") && "validate".equals(method.getName()) && method.getParameterTypes().length == 0) {
+ if (method.getReturnType() != boolean.class) {
+ logger.error("Default validation method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return boolean.");
+ throw new ConfigurationException("Default validation method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return boolean.");
+ }
+ validators.put("", clazz.getMethod(method));
+ }
+
+ if (!invalids.containsKey("") && "invalid".equals(method.getName()) && method.getParameterTypes().length == 0) {
+ if (method.getReturnType() != void.class) {
+ logger.error("Default invalid method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ throw new ConfigurationException("Default invalid method [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ }
+ invalids.put("", clazz.getMethod(method));
+ }
+ }
+
+ private static void ensureProperlyAnnotatedParameters(Method method) {
+ if (method.getParameterTypes().length != method.getParameterAnnotations().length) {
+ logger.error("Expected \"method.getParameterTypes().length != method.getParameterAnnotations().length\".");
+ throw new NocturneException("Expected \"method.getParameterTypes().length != method.getParameterAnnotations().length\".");
+ }
+
+ Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+ for (Annotation[] annotations : parameterAnnotations) {
+ boolean hasParameter = false;
+ boolean hasNamedParameter = false;
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof Parameter) {
+ hasParameter = true;
+ hasNamedParameter = !StringUtil.isEmpty(((Parameter) annotation).name());
+ }
+ }
+ if (!hasParameter) {
+ logger.error("Each parameter of the method " + method.getDeclaringClass().getName()
+ + '#' + method.getName() + " should be annotated with @Parameter.");
+ throw new ConfigurationException("Each parameter of the method " + method.getDeclaringClass().getName()
+ + '#' + method.getName() + " should be annotated with @Parameter.");
+ }
+ if (!hasNamedParameter) {
+ logger.error("Each @Parameter in the method " + method.getDeclaringClass().getName()
+ + '#' + method.getName() + " should have name.");
+ throw new ConfigurationException("Each @Parameter in the method " + method.getDeclaringClass().getName()
+ + '#' + method.getName() + " should have name.");
+ }
+ }
+ }
+
+ private void processMethod(FastClass clazz, Method method) {
+ Action action = method.getAnnotation(Action.class);
+
+ if (action != null) {
+ if (actions.containsKey(action.value())) {
+ logger.error("There are two or more methods for " +
+ clazz.getName() + " marked with @Action[" + action.value() + "].");
+ throw new ConfigurationException("There are two or more methods for " +
+ clazz.getName() + " marked with @Action[" + action.value() + "].");
+ }
+
+ ensureProperlyAnnotatedParameters(method);
+
+ if (method.getReturnType() != void.class) {
+ logger.error("Method with annotation @Action [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ throw new ConfigurationException("Method with annotation @Action [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ }
+
+ actions.put(action.value(), new ActionMethod(clazz.getMethod(method), action));
+ }
+
+ Validate validate = method.getAnnotation(Validate.class);
+
+ if (validate != null) {
+ if (validators.containsKey(validate.value())) {
+ logger.error("There are two or more methods for " +
+ clazz.getName() + " marked with @Validate[" + validate.value() + "].");
+ throw new ConfigurationException("There are two or more methods for " +
+ clazz.getName() + " marked with @Validate[" + validate.value() + "].");
+ }
+
+ ensureProperlyAnnotatedParameters(method);
+
+ if (method.getReturnType() != boolean.class) {
+ logger.error("Method with annotation @Validate [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return boolean.");
+ throw new ConfigurationException("Method with annotation @Validate [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return boolean.");
+ }
+
+ validators.put(validate.value(), clazz.getMethod(method));
+ }
+
+ Invalid invalid = method.getAnnotation(Invalid.class);
+
+ if (invalid != null) {
+ if (invalids.containsKey(invalid.value())) {
+ logger.error("There are two or more methods for " +
+ clazz.getName() + " marked with @Invalid[" + invalid.value() + "].");
+ throw new ConfigurationException("There are two or more methods for " +
+ clazz.getName() + " marked with @Invalid[" + invalid.value() + "].");
+ }
+
+ ensureProperlyAnnotatedParameters(method);
+
+ if (method.getReturnType() != void.class) {
+ logger.error("Method with annotation @Invalid [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ throw new ConfigurationException("Method with annotation @Invalid [name=" + method.getName() + ", " +
+ "class=" + clazz.getName() + "] should return void.");
+ }
+
+ invalids.put(invalid.value(), clazz.getMethod(method));
+ }
+ }
+
+ ActionMethod getActionMethod(String action) {
+ if (actions.containsKey(action)) {
+ return actions.get(action);
+ } else {
+ return actions.get("");
+ }
+ }
+
+ FastMethod getValidateMethod(String action) {
+ if (validators.containsKey(action)) {
+ return validators.get(action);
+ } else {
+ return validators.get("");
+ }
+ }
+
+ FastMethod getInvalidMethod(String action) {
+ if (invalids.containsKey(action)) {
+ return invalids.get(action);
+ } else {
+ return invalids.get("");
+ }
+ }
+
+ public static final class ActionMethod {
+ private final FastMethod method;
+ private final Action action;
+
+ private ActionMethod(FastMethod method, Action action) {
+ this.method = method;
+ this.action = action;
+ }
+
+ public FastMethod getMethod() {
+ return method;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+ }
+}
diff --git a/code/src/main/java/org/nocturne/main/ApplicationContext.java b/code/src/main/java/org/nocturne/main/ApplicationContext.java
index 6374a7a..8249968 100644
--- a/code/src/main/java/org/nocturne/main/ApplicationContext.java
+++ b/code/src/main/java/org/nocturne/main/ApplicationContext.java
@@ -1,1106 +1,1106 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.main;
-
-import com.google.common.base.Preconditions;
-import com.google.common.primitives.Ints;
-import com.google.inject.Injector;
-import org.apache.commons.lang3.ArrayUtils;
-import org.jetbrains.annotations.Contract;
-import org.nocturne.caption.Captions;
-import org.nocturne.caption.CaptionsImpl;
-import org.nocturne.collection.SingleEntryList;
-import org.nocturne.exception.ConfigurationException;
-import org.nocturne.exception.NocturneException;
-import org.nocturne.exception.ReflectionException;
-import org.nocturne.geoip.GeoIpUtil;
-import org.nocturne.link.Link;
-import org.nocturne.module.Module;
-import org.nocturne.reset.ResetStrategy;
-import org.nocturne.util.ReflectionUtil;
-import org.nocturne.util.RequestUtil;
-import org.nocturne.util.StringUtil;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.servlet.ServletContext;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.io.File;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.regex.Pattern;
-
-/**
- * This is global singleton object, accessible from all levels of
- * application. Use it to get current page and component.
- *
- * @author Mike Mirzayanov
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-public class ApplicationContext {
- private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ApplicationContext.class);
-
- /**
- * The only singleton instance.
- */
- private static final ApplicationContext INSTANCE = new ApplicationContext();
-
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- /**
- * Lock to perform synchronized operations.
- */
- private final Lock lock = new ReentrantLock();
-
- /**
- * Current page. Stored as ThreadLocal.
- */
- private static final ThreadLocal currentPage = new ThreadLocal<>();
-
- /**
- * Current component. Stored as ThreadLocal.
- */
- private static final ThreadLocal currentComponent = new ThreadLocal<>();
-
- /**
- * Is in debug mode?
- */
- private boolean debug;
-
- /**
- * List of directories to be scanned for recompiled classes. Possibly, it depends on your IDE.
- */
- private Set reloadingClassPaths;
-
- /**
- * Context path of the application.
- * Use {@code null} to use ApplicationContext.getInstance().getRequest().getContextPath().
- */
- private String contextPath;
-
- /**
- * List of listener class names.
- */
- private Set pageRequestListeners;
-
- /**
- * Request router class name.
- */
- private String requestRouter;
-
- /**
- * IoC module class name.
- */
- private String guiceModuleClassName;
-
- /**
- * List of packages (or classes) which will be reloaded using ReloadingClassLoader.
- */
- private Set classReloadingPackages;
-
- /**
- * List of packages (or classes) which should not be reloaded using ReloadingClassLoader,
- * even they are in classReloadingPackages.
- */
- private Set classReloadingExceptions;
-
- /**
- * Where to find templates. Contains relative paths from
- * deployed application root. For example: WEB-INF/templates.
- */
- private String[] templatePaths;
-
- /**
- * Indicates if template loader should stick to last successful template path
- * or always check template paths in the configured order.
- * Default value is {@code true}.
- */
- private boolean stickyTemplatePaths = true;
-
- /**
- * Preprocess FTL templates to read as component templates (if ... has found).
- */
- private boolean useComponentTemplates;
-
- /**
- * Autoimported file for all component LESS styles.
- */
- private File componentTemplatesLessCommonsFile;
-
- /**
- * Servlet context.
- */
- private ServletContext servletContext;
-
- /**
- * What page to show if RequestRouter returns {@code null}.
- */
- private String defaultPageClassName;
-
- /**
- * Pattern: if request.getServletPath() (example: /some/path) matches it, request
- * ignored by nocturne.
- */
- private Pattern skipRegex;
-
- /**
- * Default locale or English by default.
- */
- private Locale defaultLocale = new Locale("en");
-
- /**
- * Where to find caption property files, used in case of CaptionsImpl used.
- */
- private String debugCaptionsDir;
-
- /**
- * Where to find resources by DebugResourceFilter.
- */
- private String debugWebResourcesDir;
-
- /**
- * Class name for Captions implementations.
- */
- private String captionsImplClass = CaptionsImpl.class.getName();
-
- /**
- * Captions implementation instance.
- */
- private Captions captions;
-
- /**
- * Encoding for caption property files, used in case of CaptionsImpl used.
- */
- private String captionFilesEncoding = StandardCharsets.UTF_8.name();
-
- /**
- * Allowed languages (use 2-letter codes). Only English by default.
- */
- private List allowedLanguages = Collections.singletonList("en");
-
- /**
- * To use geoip to setup language by 2-letter uppercase country code (ISO 3166 code).
- * The property should have a form like: RU,BY:ru;EN,GB,US,CA:en.
- */
- private Map countryToLanguage = new HashMap<>();
-
- /**
- * Default reset strategy for fields of Components: should they be reset after request processing.
- */
- private ResetStrategy resetStrategy;
-
- /**
- * Delay between checks of template files to be changed (in seconds).
- */
- private int templatesUpdateDelay = 60;
-
- /**
- * List of annotation classes to override default strategy, should be used on classes or fields.
- */
- private Set resetAnnotations;
-
- /**
- * List of annotation classes to override default strategy, should be used on classes or fields.
- */
- private Set persistAnnotations;
-
- /**
- * Guice injector.
- */
- private Injector injector;
-
- /**
- * RequestContext for current thread.
- */
- private static final ThreadLocal requestsPerThread = new ThreadLocal<>();
-
- /**
- * Reloading class loader for current thread, used in debug mode only.
- */
- private static final ThreadLocal reloadingClassLoaderPerThread = new ThreadLocal<>();
-
- /**
- * Current reloading class loader.
- */
- private ClassLoader reloadingClassLoader = getClass().getClassLoader();
-
- /**
- * List of loaded modules.
- */
- private List modules = new ArrayList<>();
-
- /**
- * ApplicationContext is initialized.
- */
- private final AtomicBoolean initialized = new AtomicBoolean(false);
-
- private final Lock initializedLock = new ReentrantLock();
- private final Condition initializedCondition = initializedLock.newCondition();
-
- void setInitialized() {
- initializedLock.lock();
- try {
- if (!initialized.getAndSet(true)) {
- initializedCondition.signalAll();
- }
- } finally {
- initializedLock.unlock();
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- @Contract(pure = true)
- boolean isInitialized() {
- return initialized.get();
- }
-
- void setRequestAndResponse(HttpServletRequest request, HttpServletResponse response) {
- requestsPerThread.set(new RequestContext(request, response));
- }
-
- public void unsetRequestAndResponse() {
- requestsPerThread.set(new RequestContext(null, null));
- }
-
- /**
- * In debug mode it will return reloading class loader, and it
- * will return typical web-application class loader in production mode.
- *
- * @return Reloading class loader (for debug mode) and usual web-application
- * class loader (for production mode).
- */
- public ClassLoader getReloadingClassLoader() {
- if (debug) {
- return reloadingClassLoaderPerThread.get();
- } else {
- return reloadingClassLoader;
- }
- }
-
- /**
- * @return Returns application context path.
- * You should build paths in your application by
- * concatenation getContextPath() and relative path inside
- * the application.
- */
- public String getContextPath() {
- if (contextPath == null) {
- return getRequest().getContextPath();
- } else {
- return contextPath;
- }
- }
-
- /**
- * Where to find captions properties files if
- * naive org.nocturne.caption.CaptionsImpl backed used and
- * debug mode switched on.
- *
- * @return Directory or null in the production mode.
- */
- @Nullable
- public String getDebugCaptionsDir() {
- if (debug) {
- return debugCaptionsDir;
- } else {
- return null;
- }
- }
-
- /**
- * @return Default application locale, specified by
- * nocturne.default-language. English if no one specified.
- */
- public Locale getDefaultLocale() {
- return defaultLocale;
- }
-
- /**
- * @return List of allowed languages, use property nocturne.allowed-languages.
- */
- public List getAllowedLanguages() {
- return Collections.unmodifiableList(allowedLanguages);
- }
-
- /**
- * @return Map to setup language by 2-letter uppercase country code (ISO 3166 code).
- * The property should have a form like: RU,BY:ru;EN,GB,US,CA:en.
- */
- @SuppressWarnings("WeakerAccess")
- public Map getCountryToLanguage() {
- return Collections.unmodifiableMap(countryToLanguage);
- }
-
- /**
- * @return Default reset strategy for fields of Components: should they be reset after request processing.
- */
- public ResetStrategy getResetStrategy() {
- return resetStrategy;
- }
-
- /**
- * @return Delay between checks of template files to be changed (in seconds).
- */
- public int getTemplatesUpdateDelay() {
- return templatesUpdateDelay;
- }
-
- /**
- * @return List of annotation classes to override default strategy, should be used on classes or fields.
- */
- public Set getResetAnnotations() {
- return Collections.unmodifiableSet(resetAnnotations);
- }
-
- /**
- * @return List of annotation classes to override default strategy, should be used on classes or fields.
- */
- public Set getPersistAnnotations() {
- return Collections.unmodifiableSet(persistAnnotations);
- }
-
- void setTemplatesUpdateDelay(int templatesUpdateDelay) {
- this.templatesUpdateDelay = templatesUpdateDelay;
- }
-
- void setUseComponentTemplates(boolean useComponentTemplates) {
- this.useComponentTemplates = useComponentTemplates;
- }
-
- public boolean isUseComponentTemplates() {
- return useComponentTemplates;
- }
-
- public File getComponentTemplatesLessCommonsFile() {
- return componentTemplatesLessCommonsFile;
- }
-
- void setComponentTemplatesLessCommonsFile(File componentTemplatesLessCommonsFile) {
- this.componentTemplatesLessCommonsFile = componentTemplatesLessCommonsFile;
- }
-
- void setResetStrategy(ResetStrategy resetStrategy) {
- this.resetStrategy = resetStrategy;
- }
-
- void setResetAnnotations(Collection resetAnnotations) {
- this.resetAnnotations = new LinkedHashSet<>(resetAnnotations);
- }
-
- void setPersistAnnotations(Collection persistAnnotations) {
- this.persistAnnotations = new LinkedHashSet<>(persistAnnotations);
- }
-
- /**
- * @return What page to show if RequestRouter returns {@code null}.
- * Returns {@code null} if application should return 404 on it.
- */
- public String getDefaultPageClassName() {
- return defaultPageClassName;
- }
-
- /**
- * @return Encoding for caption files if
- * naive org.nocturne.caption.CaptionsImpl backed used.
- */
- public String getCaptionFilesEncoding() {
- return captionFilesEncoding;
- }
-
- /**
- * @return Current rendering frame or page.
- */
- public Component getCurrentComponent() {
- return currentComponent.get();
- }
-
- /**
- * @return Current rendering page instance.
- */
- public Page getCurrentPage() {
- return currentPage.get();
- }
-
- /**
- * Method to get application context.
- *
- * @return The only application context instance.
- */
- public static ApplicationContext getInstance() {
- return INSTANCE;
- }
-
- void setContextPath(String contextPath) {
- this.contextPath = contextPath;
- }
-
- void setLink(Link link) {
- getRequest().setAttribute("nocturne.current-page-link", link);
- }
-
- /**
- * @return Link annotation instance, which was choosen by LinkedRequestRouter as
- * link for current request.
- */
- public Link getLink() {
- return (Link) getRequest().getAttribute("nocturne.current-page-link");
- }
-
- void setDefaultPageClassName(String defaultPageClassName) {
- this.defaultPageClassName = defaultPageClassName;
- }
-
- void setCurrentPage(Page page) {
- currentPage.set(page);
- }
-
- /**
- * @return Is application in the debug mode?
- */
- public boolean isDebug() {
- return debug;
- }
-
- /**
- * @return Captions implementation class name.
- */
- public String getCaptionsImplClass() {
- return captionsImplClass;
- }
-
- void setCurrentComponent(Component component) {
- currentComponent.set(component);
- }
-
- /**
- * @return List of directories to be scanned for recompiled classes.
- * Used in the debug mode only.
- * Possibly, it depends on your IDE.
- * Setup it by nocturne.reloading-class-paths.
- */
- public List getReloadingClassPaths() {
- return new LinkedList<>(reloadingClassPaths);
- }
-
- /**
- * @return List of listener class names. Setup it by nocturne.page-request-listeners.
- */
- public List getPageRequestListeners() {
- return new LinkedList<>(pageRequestListeners);
- }
-
- void addRequestOverrideParameter(String name, String value) {
- requestsPerThread.get().addOverrideParameter(name, value);
- }
-
- void addRequestOverrideParameter(String name, List values) {
- requestsPerThread.get().addOverrideParameter(name, values);
- }
-
- Map> getRequestOverrideParameters() {
- return requestsPerThread.get().getOverrideParameters();
- }
-
- void setDebug(boolean debug) {
- this.debug = debug;
- }
-
- void setReloadingClassPaths(List reloadingClassPaths) {
- this.reloadingClassPaths = new LinkedHashSet<>(reloadingClassPaths);
- }
-
- void setPageRequestListeners(List pageRequestListeners) {
- this.pageRequestListeners = new LinkedHashSet<>(pageRequestListeners);
- }
-
- void setCaptionFilesEncoding(String captionFilesEncoding) {
- this.captionFilesEncoding = captionFilesEncoding;
- }
-
- void setRequestRouter(String requestRouter) {
- this.requestRouter = requestRouter;
- }
-
- void setTemplatePaths(String[] templatePaths) {
- this.templatePaths = Arrays.copyOf(templatePaths, templatePaths.length);
- }
-
- void setDefaultLocale(String defaultLanguage) {
- this.defaultLocale = new Locale(defaultLanguage.toLowerCase());
- }
-
- void setGuiceModuleClassName(String guiceModuleClassName) {
- this.guiceModuleClassName = guiceModuleClassName;
- }
-
- void setClassReloadingExceptions(List classReloadingExceptions) {
- this.classReloadingExceptions = new LinkedHashSet<>(classReloadingExceptions);
- }
-
- /**
- * @return Returns request router instance. Specify nocturne.request-router
- * property to set its class name.
- */
- public String getRequestRouter() {
- return requestRouter;
- }
-
- /**
- * @return Guice IoC module class name. Set nocturne.guice-module-class-name property.
- */
- public String getGuiceModuleClassName() {
- return guiceModuleClassName;
- }
-
- void setClassReloadingPackages(List classReloadingPackages) {
- this.classReloadingPackages = new LinkedHashSet<>(classReloadingPackages);
- }
-
- void setInjector(Injector injector) {
- lock.lock();
- try {
- this.injector = injector;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @return Guice injector. It is not good idea to use it.
- */
- public Injector getInjector() {
- return injector;
- }
-
- /**
- * @return List of packages (or classes) which will be reloaded using
- * ReloadingClassLoader. Set nocturne.class-reloading-packages to specify it.
- */
- public List getClassReloadingPackages() {
- return new LinkedList<>(classReloadingPackages);
- }
-
- /**
- * @return List of packages (or classes) which should not be reloaded
- * using ReloadingClassLoader, even they are in classReloadingPackages.
- * Set nocturne.class-reloading-exceptions to specify the value.
- */
- public List getClassReloadingExceptions() {
- return new LinkedList<>(classReloadingExceptions);
- }
-
- /**
- * @return Where to find templates. Contains relative paths from deployed application root. For example: WEB-INF/templates.
- * Set nocturne.template-paths - semicolon separated list of paths.
- * Set nocturne.templates-path (deprecated) - single path.
- */
- public String[] getTemplatePaths() {
- return Arrays.copyOf(templatePaths, templatePaths.length);
- }
-
- /**
- * @return Flag that indicates if template loader should stick to last successful template path
- * or always check template paths in the configured order.
- * Default value is {@code true}.
- */
- public boolean isStickyTemplatePaths() {
- return stickyTemplatePaths;
- }
-
- public void setStickyTemplatePaths(boolean stickyTemplatePaths) {
- this.stickyTemplatePaths = stickyTemplatePaths;
- }
-
- void setAllowedLanguages(List allowedLanguages) {
- this.allowedLanguages = new ArrayList<>(allowedLanguages);
- }
-
- void setCountryToLanguage(Map countryToLanguage) {
- this.countryToLanguage = new HashMap<>(countryToLanguage);
- }
-
- void setServletContext(ServletContext servletContext) {
- this.servletContext = servletContext;
- }
-
- void setDebugCaptionsDir(String debugCaptionsDir) {
- this.debugCaptionsDir = debugCaptionsDir;
- }
-
- /**
- * @return Returns current servlet context.
- */
- public ServletContext getServletContext() {
- return servletContext;
- }
-
-
- /**
- * @return if request.getServletPath() (example: /some/path) matches it, request ignored by nocturne.
- * Use nocturne.skip-regex to set it.
- */
- public Pattern getSkipRegex() {
- return skipRegex;
- }
-
- void setCaptionsImplClass(String captionsImplClass) {
- this.captionsImplClass = captionsImplClass;
- }
-
- void setSkipRegex(Pattern skipRegex) {
- this.skipRegex = skipRegex;
- }
-
- public boolean hasRequest() {
- RequestContext requestContext = requestsPerThread.get();
- return requestContext != null && requestContext.getRequest() != null;
- }
-
- /**
- * @return Returns current servlet request instance.
- */
- public HttpServletRequest getRequest() {
- return requestsPerThread.get().getRequest();
- }
-
- public boolean hasResponse() {
- RequestContext requestContext = requestsPerThread.get();
- return requestContext != null && requestContext.getResponse() != null;
- }
-
- /**
- * @return Returns current servlet response instance.
- */
- public HttpServletResponse getResponse() {
- return requestsPerThread.get().getResponse();
- }
-
- void setReloadingClassLoader(ClassLoader loader) {
- if (debug) {
- reloadingClassLoaderPerThread.set(loader);
- } else {
- reloadingClassLoader = loader;
- }
- }
-
- /**
- * Modules use the method to update reloading class path.
- * Do not use it from your code.
- *
- * @param dir Directory to be added.
- */
- public void addReloadingClassPath(File dir) {
- if (!dir.isDirectory()) {
- logger.error("Path " + dir.getName() + " expected to be a directory.");
- throw new ConfigurationException("Path " + dir.getName() + " expected to be a directory.");
- }
- reloadingClassPaths.add(dir);
-
- if (debug) {
- ReloadingContext context = ReloadingContext.getInstance();
- try {
- ReflectionUtil.invoke(context, "addReloadingClassPath", dir);
- } catch (ReflectionException e) {
- logger.error("Can't call addReloadingClassPath for ReloadingContext.", e);
- throw new NocturneException("Can't call addReloadingClassPath for ReloadingContext.", e);
- }
- } else {
- ReloadingContext.getInstance().addReloadingClassPath(dir);
- }
- }
-
- public void addClassReloadingException(String packageOrClassName) {
- if (debug) {
- classReloadingExceptions.add(packageOrClassName);
- ReloadingContext.getInstance().addClassReloadingException(packageOrClassName);
- }
- }
-
- /**
- * @param shortcut Shortcut value.
- * @return Use the method to work with captions from your code.
- * Usually, it is not good idea, because captions are part of view layer.
- */
- @SuppressWarnings("DollarSignInName")
- public String $(String shortcut) {
- return $(shortcut, ArrayUtils.EMPTY_OBJECT_ARRAY);
- }
-
- /**
- * @param shortcut Shortcut value.
- * @param args Shortcut arguments.
- * @return Use the method to work with captions from your code.
- * Usually, it is not good idea, because captions are part of view layer.
- */
- @SuppressWarnings({"OverloadedVarargsMethod", "DollarSignInName"})
- public String $(String shortcut, Object... args) {
- shortcut = shortcut.trim();
- initializeCaptions();
- return captions.find(shortcut, args);
- }
-
- /**
- * @param locale Expected locale.
- * @param shortcut Shortcut value.
- * @param args Shortcut arguments.
- * @return Use the method to work with captions from your code.
- * Usually, it is not good idea, because captions are part of view layer.
- */
- @SuppressWarnings("OverloadedVarargsMethod")
- public String getCaption(Locale locale, String shortcut, Object... args) {
- shortcut = shortcut.trim();
- initializeCaptions();
- return captions.find(locale, shortcut, args);
- }
-
- /**
- * @param locale Expected locale.
- * @param shortcut Shortcut value.
- * @return Use the method to work with captions from your code.
- * Usually, it is not good idea, because captions are part of view layer.
- */
- public String getCaption(Locale locale, String shortcut) {
- shortcut = shortcut.trim();
- initializeCaptions();
- return captions.find(locale, shortcut);
- }
-
- @SuppressWarnings("unchecked")
- private void initializeCaptions() {
- if (captions != null) {
- return;
- }
-
- lock.lock();
- try {
- Class extends Captions> clazz = (Class extends Captions>) getClass().getClassLoader().loadClass(captionsImplClass);
- captions = injector.getInstance(clazz);
- } catch (ClassNotFoundException e) {
- logger.error("Class " + captionsImplClass + " not found.", e);
- throw new ConfigurationException("Class " + captionsImplClass + " should implement Captions.", e);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @return Locale for current request.
- */
- public Locale getLocale() {
- return requestsPerThread.get().getLocale();
- }
-
- /**
- * @return List of loaded modules.
- */
- public List getModules() {
- return Collections.unmodifiableList(modules);
- }
-
- void setModules(List modules) {
- this.modules = new ArrayList<>(modules);
- }
-
- /**
- * @return Prefix before attributes in request which
- * will be injected as parameters in Components.
- */
- public static String getAdditionalParamsRequestAttributePrefix() {
- return "nocturne.additional-parameter.";
- }
-
- private static String getActionRequestPageClassName() {
- return "nocturne.request-page-class-name";
- }
-
- private static String getActionRequestParamName() {
- return "nocturne.request-action";
- }
-
- void setRequestAction(String action) {
- getRequest().setAttribute(getActionRequestParamName(), action);
- }
-
- /**
- * @return Action for current request (how request router decided). Empty string if
- * no one specified. Typically, gets from action parameter (example: ?action=test)
- * or link template (example: "user/{action}").
- */
- public String getRequestAction() {
- return (String) getRequest().getAttribute(getActionRequestParamName());
- }
-
- void setRequestPageClassName(String pageClassName) {
- getRequest().setAttribute(getActionRequestPageClassName(), pageClassName);
- }
-
- /**
- * @return Page class name for current request (how request router decided).
- */
- public String getRequestPageClassName() {
- return (String) getRequest().getAttribute(getActionRequestPageClassName());
- }
-
- public void setDebugWebResourcesDir(String debugWebResourcesDir) {
- this.debugWebResourcesDir = debugWebResourcesDir;
- }
-
- /**
- * @return Returns the directory where to find resources by DebugResourceFilter.
- */
- public String getDebugWebResourcesDir() {
- return debugWebResourcesDir;
- }
-
- /**
- * @param runnable Runnable to be executed after ApplicationContext has been initialized.
- */
- public void executeAfterInitialization(Runnable runnable) {
- new Thread(() -> {
- initializedLock.lock();
- try {
- while (!isInitialized()) {
- try {
- initializedCondition.await();
- } catch (InterruptedException ignored) {
- // No operations.
- }
- }
- runnable.run();
- } finally {
- initializedLock.unlock();
- }
- }).start();
- }
-
- /**
- * Stores current request context: request, response and locale.
- */
- private static final class RequestContext {
- private static final Pattern ACCEPT_LANGUAGE_SPLIT_PATTERN = Pattern.compile("[,;-]");
- private static final String LANGUAGE_COOKIE_NAME = "nocturne.language";
- /**
- * Http servlet request.
- */
- private final HttpServletRequest request;
-
- /**
- * Http servlet response.
- */
- private final HttpServletResponse response;
-
- /**
- * Locale for current request.
- */
- private Locale locale;
-
- /**
- * Parameters which override request params.
- */
- private Map> overrideParameters;
-
- private RequestContext(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response) {
- if ((request == null) ^ (response == null)) {
- logger.error("It is not possible case '(request == null) ^ (response == null)'.");
- throw new IllegalArgumentException("It is not possible case '(request == null) ^ (response == null)'.");
- }
-
- this.request = request;
- this.response = response;
-
- if (request == null) {
- this.locale = null;
- } else {
- setupLocale();
- }
- }
-
- /**
- * @return Http servlet request.
- */
- public HttpServletRequest getRequest() {
- return request;
- }
-
- /**
- * @return Http servlet response.
- */
- public HttpServletResponse getResponse() {
- return response;
- }
-
- /**
- * @return Locale for current request.
- * Uses lang, language, locale parameters to find
- * current locale: 2-letter language code.
- * If it specified once, stores current locale in the session.
- */
- public Locale getLocale() {
- return locale;
- }
-
- @Contract(value = "null -> true", pure = true)
- private static boolean isInvalidLanguage(@Nullable String lang) {
- return lang == null || lang.length() != 2;
- }
-
- private void setupLocale() {
- Map> requestMap = RequestUtil.getRequestParams(request);
-
- String lang = RequestUtil.getFirst(requestMap, "lang");
-
- if (isInvalidLanguage(lang)) {
- lang = RequestUtil.getFirst(requestMap, "language");
- }
-
- if (isInvalidLanguage(lang)) {
- lang = RequestUtil.getFirst(requestMap, "locale");
- }
-
- if (isInvalidLanguage(lang)) {
- HttpSession session = request.getSession(false);
- if (session != null) {
- lang = (String) session.getAttribute("nocturne.language");
- }
-
- if (isInvalidLanguage(lang)) {
- lang = getCookie(LANGUAGE_COOKIE_NAME);
- }
-
- if (isInvalidLanguage(lang)) {
- lang = getLanguageByGeoIp();
- if (isInvalidLanguage(lang)) {
- String[] languages = getAcceptLanguages();
- for (String language : languages) {
- if (getInstance().getAllowedLanguages().contains(language)
- && !"en".equalsIgnoreCase(language)) {
- lang = language;
- break;
- }
- }
-
- if (isInvalidLanguage(lang)) {
- for (String language : languages) {
- if (getInstance().getAllowedLanguages().contains(language)) {
- lang = language;
- break;
- }
- }
- }
- }
- }
- locale = localeByLanguage(lang);
- } else {
- locale = localeByLanguage(lang);
- request.getSession().setAttribute("nocturne.language", locale.getLanguage());
- addCookie(LANGUAGE_COOKIE_NAME, lang, TimeUnit.DAYS.toSeconds(30));
- }
- }
-
- @SuppressWarnings("SameParameterValue")
- @Nullable
- private String getCookie(String cookieName) {
- Cookie[] cookies = request.getCookies();
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- if (cookieName.equals(cookie.getName())) {
- return cookie.getValue();
- }
- }
- }
- return null;
- }
-
- @SuppressWarnings("SameParameterValue")
- private void addCookie(String cookieName, String cookieValue, long maxAge) {
- boolean updated = false;
- if (request.getCookies() != null) {
- for (Cookie cookie : request.getCookies()) {
- if (cookie.getName().equals(cookieName)) {
- cookie.setValue(cookieValue);
- if (updated) {
- cookie.setMaxAge(0);
- } else {
- cookie.setMaxAge(Ints.checkedCast(maxAge));
- }
- cookie.setPath("/");
- response.addCookie(cookie);
- updated = true;
- }
- }
- }
-
- if (!updated) {
- Cookie cookie = new Cookie(cookieName, cookieValue);
- cookie.setMaxAge(Ints.checkedCast(maxAge));
- cookie.setPath("/");
- response.addCookie(cookie);
- }
- }
-
- @Nullable
- private String getLanguageByGeoIp() {
- String countryCode = null; // GeoIpUtil.getCountryCode(request);
- String lang = getInstance().getCountryToLanguage().get(countryCode);
- String[] languages = getAcceptLanguages();
-
- if (ArrayUtils.indexOf(languages, lang) >= 0 && getInstance().getAllowedLanguages().contains(lang)) {
- return lang;
- }
-
- return null;
- }
-
- private String[] getAcceptLanguages() {
- String header = request.getHeader("Accept-Language");
-
- if (StringUtil.isEmpty(header)) {
- return EMPTY_STRING_ARRAY;
- } else {
- String[] result = ACCEPT_LANGUAGE_SPLIT_PATTERN.split(header);
- for (int i = 0; i < result.length; ++i) {
- result[i] = result[i].toLowerCase();
- }
- return result;
- }
- }
-
- @Nonnull
- private static Locale localeByLanguage(@Nullable String language) {
- if (getInstance().getAllowedLanguages().contains(language)) {
- return new Locale(Preconditions.checkNotNull(language));
- } else {
- return getInstance().getDefaultLocale();
- }
- }
-
- private void addOverrideParameter(String name, String value) {
- if (overrideParameters == null) {
- overrideParameters = new HashMap<>();
- }
-
- overrideParameters.put(name, new SingleEntryList<>(value));
- }
-
- private void addOverrideParameter(String name, Collection values) {
- if (overrideParameters == null) {
- overrideParameters = new HashMap<>();
- }
-
- overrideParameters.put(name, new ArrayList<>(values));
- }
-
- private Map> getOverrideParameters() {
- return overrideParameters;
- }
- }
-}
+/*
+ * Copyright 2009 Mike Mirzayanov
+ */
+package org.nocturne.main;
+
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Ints;
+import com.google.inject.Injector;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jetbrains.annotations.Contract;
+import org.nocturne.caption.Captions;
+import org.nocturne.caption.CaptionsImpl;
+import org.nocturne.collection.SingleEntryList;
+import org.nocturne.exception.ConfigurationException;
+import org.nocturne.exception.NocturneException;
+import org.nocturne.exception.ReflectionException;
+import org.nocturne.geoip.GeoIpUtil;
+import org.nocturne.link.Link;
+import org.nocturne.module.Module;
+import org.nocturne.reset.ResetStrategy;
+import org.nocturne.util.ReflectionUtil;
+import org.nocturne.util.RequestUtil;
+import org.nocturne.util.StringUtil;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Pattern;
+
+/**
+ * This is global singleton object, accessible from all levels of
+ * application. Use it to get current page and component.
+ *
+ * @author Mike Mirzayanov
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ApplicationContext {
+ private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ApplicationContext.class);
+
+ /**
+ * The only singleton instance.
+ */
+ private static final ApplicationContext INSTANCE = new ApplicationContext();
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ /**
+ * Lock to perform synchronized operations.
+ */
+ private final Lock lock = new ReentrantLock();
+
+ /**
+ * Current page. Stored as ThreadLocal.
+ */
+ private static final ThreadLocal currentPage = new ThreadLocal<>();
+
+ /**
+ * Current component. Stored as ThreadLocal.
+ */
+ private static final ThreadLocal currentComponent = new ThreadLocal<>();
+
+ /**
+ * Is in debug mode?
+ */
+ private boolean debug;
+
+ /**
+ * List of directories to be scanned for recompiled classes. Possibly, it depends on your IDE.
+ */
+ private Set reloadingClassPaths;
+
+ /**
+ * Context path of the application.
+ * Use {@code null} to use ApplicationContext.getInstance().getRequest().getContextPath().
+ */
+ private String contextPath;
+
+ /**
+ * List of listener class names.
+ */
+ private Set pageRequestListeners;
+
+ /**
+ * Request router class name.
+ */
+ private String requestRouter;
+
+ /**
+ * IoC module class name.
+ */
+ private String guiceModuleClassName;
+
+ /**
+ * List of packages (or classes) which will be reloaded using ReloadingClassLoader.
+ */
+ private Set classReloadingPackages;
+
+ /**
+ * List of packages (or classes) which should not be reloaded using ReloadingClassLoader,
+ * even they are in classReloadingPackages.
+ */
+ private Set classReloadingExceptions;
+
+ /**
+ * Where to find templates. Contains relative paths from
+ * deployed application root. For example: WEB-INF/templates.
+ */
+ private String[] templatePaths;
+
+ /**
+ * Indicates if template loader should stick to last successful template path
+ * or always check template paths in the configured order.
+ * Default value is {@code true}.
+ */
+ private boolean stickyTemplatePaths = true;
+
+ /**
+ * Preprocess FTL templates to read as component templates (if ... has found).
+ */
+ private boolean useComponentTemplates;
+
+ /**
+ * Autoimported file for all component LESS styles.
+ */
+ private File componentTemplatesLessCommonsFile;
+
+ /**
+ * Servlet context.
+ */
+ private ServletContext servletContext;
+
+ /**
+ * What page to show if RequestRouter returns {@code null}.
+ */
+ private String defaultPageClassName;
+
+ /**
+ * Pattern: if request.getServletPath() (example: /some/path) matches it, request
+ * ignored by nocturne.
+ */
+ private Pattern skipRegex;
+
+ /**
+ * Default locale or English by default.
+ */
+ private Locale defaultLocale = new Locale("en");
+
+ /**
+ * Where to find caption property files, used in case of CaptionsImpl used.
+ */
+ private String debugCaptionsDir;
+
+ /**
+ * Where to find resources by DebugResourceFilter.
+ */
+ private String debugWebResourcesDir;
+
+ /**
+ * Class name for Captions implementations.
+ */
+ private String captionsImplClass = CaptionsImpl.class.getName();
+
+ /**
+ * Captions implementation instance.
+ */
+ private Captions captions;
+
+ /**
+ * Encoding for caption property files, used in case of CaptionsImpl used.
+ */
+ private String captionFilesEncoding = StandardCharsets.UTF_8.name();
+
+ /**
+ * Allowed languages (use 2-letter codes). Only English by default.
+ */
+ private List allowedLanguages = Collections.singletonList("en");
+
+ /**
+ * To use geoip to setup language by 2-letter uppercase country code (ISO 3166 code).
+ * The property should have a form like: RU,BY:ru;EN,GB,US,CA:en.
+ */
+ private Map countryToLanguage = new HashMap<>();
+
+ /**
+ * Default reset strategy for fields of Components: should they be reset after request processing.
+ */
+ private ResetStrategy resetStrategy;
+
+ /**
+ * Delay between checks of template files to be changed (in seconds).
+ */
+ private int templatesUpdateDelay = 60;
+
+ /**
+ * List of annotation classes to override default strategy, should be used on classes or fields.
+ */
+ private Set resetAnnotations;
+
+ /**
+ * List of annotation classes to override default strategy, should be used on classes or fields.
+ */
+ private Set persistAnnotations;
+
+ /**
+ * Guice injector.
+ */
+ private Injector injector;
+
+ /**
+ * RequestContext for current thread.
+ */
+ private static final ThreadLocal requestsPerThread = new ThreadLocal<>();
+
+ /**
+ * Reloading class loader for current thread, used in debug mode only.
+ */
+ private static final ThreadLocal reloadingClassLoaderPerThread = new ThreadLocal<>();
+
+ /**
+ * Current reloading class loader.
+ */
+ private ClassLoader reloadingClassLoader = getClass().getClassLoader();
+
+ /**
+ * List of loaded modules.
+ */
+ private List modules = new ArrayList<>();
+
+ /**
+ * ApplicationContext is initialized.
+ */
+ private final AtomicBoolean initialized = new AtomicBoolean(false);
+
+ private final Lock initializedLock = new ReentrantLock();
+ private final Condition initializedCondition = initializedLock.newCondition();
+
+ void setInitialized() {
+ initializedLock.lock();
+ try {
+ if (!initialized.getAndSet(true)) {
+ initializedCondition.signalAll();
+ }
+ } finally {
+ initializedLock.unlock();
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Contract(pure = true)
+ boolean isInitialized() {
+ return initialized.get();
+ }
+
+ void setRequestAndResponse(HttpServletRequest request, HttpServletResponse response) {
+ requestsPerThread.set(new RequestContext(request, response));
+ }
+
+ public void unsetRequestAndResponse() {
+ requestsPerThread.set(new RequestContext(null, null));
+ }
+
+ /**
+ * In debug mode it will return reloading class loader, and it
+ * will return typical web-application class loader in production mode.
+ *
+ * @return Reloading class loader (for debug mode) and usual web-application
+ * class loader (for production mode).
+ */
+ public ClassLoader getReloadingClassLoader() {
+ if (debug) {
+ return reloadingClassLoaderPerThread.get();
+ } else {
+ return reloadingClassLoader;
+ }
+ }
+
+ /**
+ * @return Returns application context path.
+ * You should build paths in your application by
+ * concatenation getContextPath() and relative path inside
+ * the application.
+ */
+ public String getContextPath() {
+ if (contextPath == null) {
+ return getRequest().getContextPath();
+ } else {
+ return contextPath;
+ }
+ }
+
+ /**
+ * Where to find captions properties files if
+ * naive org.nocturne.caption.CaptionsImpl backed used and
+ * debug mode switched on.
+ *
+ * @return Directory or null in the production mode.
+ */
+ @Nullable
+ public String getDebugCaptionsDir() {
+ if (debug) {
+ return debugCaptionsDir;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return Default application locale, specified by
+ * nocturne.default-language. English if no one specified.
+ */
+ public Locale getDefaultLocale() {
+ return defaultLocale;
+ }
+
+ /**
+ * @return List of allowed languages, use property nocturne.allowed-languages.
+ */
+ public List getAllowedLanguages() {
+ return Collections.unmodifiableList(allowedLanguages);
+ }
+
+ /**
+ * @return Map to setup language by 2-letter uppercase country code (ISO 3166 code).
+ * The property should have a form like: RU,BY:ru;EN,GB,US,CA:en.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public Map getCountryToLanguage() {
+ return Collections.unmodifiableMap(countryToLanguage);
+ }
+
+ /**
+ * @return Default reset strategy for fields of Components: should they be reset after request processing.
+ */
+ public ResetStrategy getResetStrategy() {
+ return resetStrategy;
+ }
+
+ /**
+ * @return Delay between checks of template files to be changed (in seconds).
+ */
+ public int getTemplatesUpdateDelay() {
+ return templatesUpdateDelay;
+ }
+
+ /**
+ * @return List of annotation classes to override default strategy, should be used on classes or fields.
+ */
+ public Set getResetAnnotations() {
+ return Collections.unmodifiableSet(resetAnnotations);
+ }
+
+ /**
+ * @return List of annotation classes to override default strategy, should be used on classes or fields.
+ */
+ public Set getPersistAnnotations() {
+ return Collections.unmodifiableSet(persistAnnotations);
+ }
+
+ void setTemplatesUpdateDelay(int templatesUpdateDelay) {
+ this.templatesUpdateDelay = templatesUpdateDelay;
+ }
+
+ void setUseComponentTemplates(boolean useComponentTemplates) {
+ this.useComponentTemplates = useComponentTemplates;
+ }
+
+ public boolean isUseComponentTemplates() {
+ return useComponentTemplates;
+ }
+
+ public File getComponentTemplatesLessCommonsFile() {
+ return componentTemplatesLessCommonsFile;
+ }
+
+ void setComponentTemplatesLessCommonsFile(File componentTemplatesLessCommonsFile) {
+ this.componentTemplatesLessCommonsFile = componentTemplatesLessCommonsFile;
+ }
+
+ void setResetStrategy(ResetStrategy resetStrategy) {
+ this.resetStrategy = resetStrategy;
+ }
+
+ void setResetAnnotations(Collection resetAnnotations) {
+ this.resetAnnotations = new LinkedHashSet<>(resetAnnotations);
+ }
+
+ void setPersistAnnotations(Collection persistAnnotations) {
+ this.persistAnnotations = new LinkedHashSet<>(persistAnnotations);
+ }
+
+ /**
+ * @return What page to show if RequestRouter returns {@code null}.
+ * Returns {@code null} if application should return 404 on it.
+ */
+ public String getDefaultPageClassName() {
+ return defaultPageClassName;
+ }
+
+ /**
+ * @return Encoding for caption files if
+ * naive org.nocturne.caption.CaptionsImpl backed used.
+ */
+ public String getCaptionFilesEncoding() {
+ return captionFilesEncoding;
+ }
+
+ /**
+ * @return Current rendering frame or page.
+ */
+ public Component getCurrentComponent() {
+ return currentComponent.get();
+ }
+
+ /**
+ * @return Current rendering page instance.
+ */
+ public Page getCurrentPage() {
+ return currentPage.get();
+ }
+
+ /**
+ * Method to get application context.
+ *
+ * @return The only application context instance.
+ */
+ public static ApplicationContext getInstance() {
+ return INSTANCE;
+ }
+
+ void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ void setLink(Link link) {
+ getRequest().setAttribute("nocturne.current-page-link", link);
+ }
+
+ /**
+ * @return Link annotation instance, which was choosen by LinkedRequestRouter as
+ * link for current request.
+ */
+ public Link getLink() {
+ return (Link) getRequest().getAttribute("nocturne.current-page-link");
+ }
+
+ void setDefaultPageClassName(String defaultPageClassName) {
+ this.defaultPageClassName = defaultPageClassName;
+ }
+
+ void setCurrentPage(Page page) {
+ currentPage.set(page);
+ }
+
+ /**
+ * @return Is application in the debug mode?
+ */
+ public boolean isDebug() {
+ return debug;
+ }
+
+ /**
+ * @return Captions implementation class name.
+ */
+ public String getCaptionsImplClass() {
+ return captionsImplClass;
+ }
+
+ void setCurrentComponent(Component component) {
+ currentComponent.set(component);
+ }
+
+ /**
+ * @return List of directories to be scanned for recompiled classes.
+ * Used in the debug mode only.
+ * Possibly, it depends on your IDE.
+ * Setup it by nocturne.reloading-class-paths.
+ */
+ public List getReloadingClassPaths() {
+ return new LinkedList<>(reloadingClassPaths);
+ }
+
+ /**
+ * @return List of listener class names. Setup it by nocturne.page-request-listeners.
+ */
+ public List getPageRequestListeners() {
+ return new LinkedList<>(pageRequestListeners);
+ }
+
+ void addRequestOverrideParameter(String name, String value) {
+ requestsPerThread.get().addOverrideParameter(name, value);
+ }
+
+ void addRequestOverrideParameter(String name, List values) {
+ requestsPerThread.get().addOverrideParameter(name, values);
+ }
+
+ Map> getRequestOverrideParameters() {
+ return requestsPerThread.get().getOverrideParameters();
+ }
+
+ void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ void setReloadingClassPaths(List reloadingClassPaths) {
+ this.reloadingClassPaths = new LinkedHashSet<>(reloadingClassPaths);
+ }
+
+ void setPageRequestListeners(List pageRequestListeners) {
+ this.pageRequestListeners = new LinkedHashSet<>(pageRequestListeners);
+ }
+
+ void setCaptionFilesEncoding(String captionFilesEncoding) {
+ this.captionFilesEncoding = captionFilesEncoding;
+ }
+
+ void setRequestRouter(String requestRouter) {
+ this.requestRouter = requestRouter;
+ }
+
+ void setTemplatePaths(String[] templatePaths) {
+ this.templatePaths = Arrays.copyOf(templatePaths, templatePaths.length);
+ }
+
+ void setDefaultLocale(String defaultLanguage) {
+ this.defaultLocale = new Locale(defaultLanguage.toLowerCase());
+ }
+
+ void setGuiceModuleClassName(String guiceModuleClassName) {
+ this.guiceModuleClassName = guiceModuleClassName;
+ }
+
+ void setClassReloadingExceptions(List classReloadingExceptions) {
+ this.classReloadingExceptions = new LinkedHashSet<>(classReloadingExceptions);
+ }
+
+ /**
+ * @return Returns request router instance. Specify nocturne.request-router
+ * property to set its class name.
+ */
+ public String getRequestRouter() {
+ return requestRouter;
+ }
+
+ /**
+ * @return Guice IoC module class name. Set nocturne.guice-module-class-name property.
+ */
+ public String getGuiceModuleClassName() {
+ return guiceModuleClassName;
+ }
+
+ void setClassReloadingPackages(List classReloadingPackages) {
+ this.classReloadingPackages = new LinkedHashSet<>(classReloadingPackages);
+ }
+
+ void setInjector(Injector injector) {
+ lock.lock();
+ try {
+ this.injector = injector;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @return Guice injector. It is not good idea to use it.
+ */
+ public Injector getInjector() {
+ return injector;
+ }
+
+ /**
+ * @return List of packages (or classes) which will be reloaded using
+ * ReloadingClassLoader. Set nocturne.class-reloading-packages to specify it.
+ */
+ public List getClassReloadingPackages() {
+ return new LinkedList<>(classReloadingPackages);
+ }
+
+ /**
+ * @return List of packages (or classes) which should not be reloaded
+ * using ReloadingClassLoader, even they are in classReloadingPackages.
+ * Set nocturne.class-reloading-exceptions to specify the value.
+ */
+ public List getClassReloadingExceptions() {
+ return new LinkedList<>(classReloadingExceptions);
+ }
+
+ /**
+ * @return Where to find templates. Contains relative paths from deployed application root. For example: WEB-INF/templates.
+ * Set nocturne.template-paths - semicolon separated list of paths.
+ * Set nocturne.templates-path (deprecated) - single path.
+ */
+ public String[] getTemplatePaths() {
+ return Arrays.copyOf(templatePaths, templatePaths.length);
+ }
+
+ /**
+ * @return Flag that indicates if template loader should stick to last successful template path
+ * or always check template paths in the configured order.
+ * Default value is {@code true}.
+ */
+ public boolean isStickyTemplatePaths() {
+ return stickyTemplatePaths;
+ }
+
+ public void setStickyTemplatePaths(boolean stickyTemplatePaths) {
+ this.stickyTemplatePaths = stickyTemplatePaths;
+ }
+
+ void setAllowedLanguages(List allowedLanguages) {
+ this.allowedLanguages = new ArrayList<>(allowedLanguages);
+ }
+
+ void setCountryToLanguage(Map countryToLanguage) {
+ this.countryToLanguage = new HashMap<>(countryToLanguage);
+ }
+
+ void setServletContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ void setDebugCaptionsDir(String debugCaptionsDir) {
+ this.debugCaptionsDir = debugCaptionsDir;
+ }
+
+ /**
+ * @return Returns current servlet context.
+ */
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+
+ /**
+ * @return if request.getServletPath() (example: /some/path) matches it, request ignored by nocturne.
+ * Use nocturne.skip-regex to set it.
+ */
+ public Pattern getSkipRegex() {
+ return skipRegex;
+ }
+
+ void setCaptionsImplClass(String captionsImplClass) {
+ this.captionsImplClass = captionsImplClass;
+ }
+
+ void setSkipRegex(Pattern skipRegex) {
+ this.skipRegex = skipRegex;
+ }
+
+ public boolean hasRequest() {
+ RequestContext requestContext = requestsPerThread.get();
+ return requestContext != null && requestContext.getRequest() != null;
+ }
+
+ /**
+ * @return Returns current servlet request instance.
+ */
+ public HttpServletRequest getRequest() {
+ return requestsPerThread.get().getRequest();
+ }
+
+ public boolean hasResponse() {
+ RequestContext requestContext = requestsPerThread.get();
+ return requestContext != null && requestContext.getResponse() != null;
+ }
+
+ /**
+ * @return Returns current servlet response instance.
+ */
+ public HttpServletResponse getResponse() {
+ return requestsPerThread.get().getResponse();
+ }
+
+ void setReloadingClassLoader(ClassLoader loader) {
+ if (debug) {
+ reloadingClassLoaderPerThread.set(loader);
+ } else {
+ reloadingClassLoader = loader;
+ }
+ }
+
+ /**
+ * Modules use the method to update reloading class path.
+ * Do not use it from your code.
+ *
+ * @param dir Directory to be added.
+ */
+ public void addReloadingClassPath(File dir) {
+ if (!dir.isDirectory()) {
+ logger.error("Path " + dir.getName() + " expected to be a directory.");
+ throw new ConfigurationException("Path " + dir.getName() + " expected to be a directory.");
+ }
+ reloadingClassPaths.add(dir);
+
+ if (debug) {
+ ReloadingContext context = ReloadingContext.getInstance();
+ try {
+ ReflectionUtil.invoke(context, "addReloadingClassPath", dir);
+ } catch (ReflectionException e) {
+ logger.error("Can't call addReloadingClassPath for ReloadingContext.", e);
+ throw new NocturneException("Can't call addReloadingClassPath for ReloadingContext.", e);
+ }
+ } else {
+ ReloadingContext.getInstance().addReloadingClassPath(dir);
+ }
+ }
+
+ public void addClassReloadingException(String packageOrClassName) {
+ if (debug) {
+ classReloadingExceptions.add(packageOrClassName);
+ ReloadingContext.getInstance().addClassReloadingException(packageOrClassName);
+ }
+ }
+
+ /**
+ * @param shortcut Shortcut value.
+ * @return Use the method to work with captions from your code.
+ * Usually, it is not good idea, because captions are part of view layer.
+ */
+ @SuppressWarnings("DollarSignInName")
+ public String $(String shortcut) {
+ return $(shortcut, ArrayUtils.EMPTY_OBJECT_ARRAY);
+ }
+
+ /**
+ * @param shortcut Shortcut value.
+ * @param args Shortcut arguments.
+ * @return Use the method to work with captions from your code.
+ * Usually, it is not good idea, because captions are part of view layer.
+ */
+ @SuppressWarnings({"OverloadedVarargsMethod", "DollarSignInName"})
+ public String $(String shortcut, Object... args) {
+ shortcut = shortcut.trim();
+ initializeCaptions();
+ return captions.find(shortcut, args);
+ }
+
+ /**
+ * @param locale Expected locale.
+ * @param shortcut Shortcut value.
+ * @param args Shortcut arguments.
+ * @return Use the method to work with captions from your code.
+ * Usually, it is not good idea, because captions are part of view layer.
+ */
+ @SuppressWarnings("OverloadedVarargsMethod")
+ public String getCaption(Locale locale, String shortcut, Object... args) {
+ shortcut = shortcut.trim();
+ initializeCaptions();
+ return captions.find(locale, shortcut, args);
+ }
+
+ /**
+ * @param locale Expected locale.
+ * @param shortcut Shortcut value.
+ * @return Use the method to work with captions from your code.
+ * Usually, it is not good idea, because captions are part of view layer.
+ */
+ public String getCaption(Locale locale, String shortcut) {
+ shortcut = shortcut.trim();
+ initializeCaptions();
+ return captions.find(locale, shortcut);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initializeCaptions() {
+ if (captions != null) {
+ return;
+ }
+
+ lock.lock();
+ try {
+ Class extends Captions> clazz = (Class extends Captions>) getClass().getClassLoader().loadClass(captionsImplClass);
+ captions = injector.getInstance(clazz);
+ } catch (ClassNotFoundException e) {
+ logger.error("Class " + captionsImplClass + " not found.", e);
+ throw new ConfigurationException("Class " + captionsImplClass + " should implement Captions.", e);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @return Locale for current request.
+ */
+ public Locale getLocale() {
+ return requestsPerThread.get().getLocale();
+ }
+
+ /**
+ * @return List of loaded modules.
+ */
+ public List getModules() {
+ return Collections.unmodifiableList(modules);
+ }
+
+ void setModules(List modules) {
+ this.modules = new ArrayList<>(modules);
+ }
+
+ /**
+ * @return Prefix before attributes in request which
+ * will be injected as parameters in Components.
+ */
+ public static String getAdditionalParamsRequestAttributePrefix() {
+ return "nocturne.additional-parameter.";
+ }
+
+ private static String getActionRequestPageClassName() {
+ return "nocturne.request-page-class-name";
+ }
+
+ private static String getActionRequestParamName() {
+ return "nocturne.request-action";
+ }
+
+ void setRequestAction(String action) {
+ getRequest().setAttribute(getActionRequestParamName(), action);
+ }
+
+ /**
+ * @return Action for current request (how request router decided). Empty string if
+ * no one specified. Typically, gets from action parameter (example: ?action=test)
+ * or link template (example: "user/{action}").
+ */
+ public String getRequestAction() {
+ return (String) getRequest().getAttribute(getActionRequestParamName());
+ }
+
+ void setRequestPageClassName(String pageClassName) {
+ getRequest().setAttribute(getActionRequestPageClassName(), pageClassName);
+ }
+
+ /**
+ * @return Page class name for current request (how request router decided).
+ */
+ public String getRequestPageClassName() {
+ return (String) getRequest().getAttribute(getActionRequestPageClassName());
+ }
+
+ public void setDebugWebResourcesDir(String debugWebResourcesDir) {
+ this.debugWebResourcesDir = debugWebResourcesDir;
+ }
+
+ /**
+ * @return Returns the directory where to find resources by DebugResourceFilter.
+ */
+ public String getDebugWebResourcesDir() {
+ return debugWebResourcesDir;
+ }
+
+ /**
+ * @param runnable Runnable to be executed after ApplicationContext has been initialized.
+ */
+ public void executeAfterInitialization(Runnable runnable) {
+ new Thread(() -> {
+ initializedLock.lock();
+ try {
+ while (!isInitialized()) {
+ try {
+ initializedCondition.await();
+ } catch (InterruptedException ignored) {
+ // No operations.
+ }
+ }
+ runnable.run();
+ } finally {
+ initializedLock.unlock();
+ }
+ }).start();
+ }
+
+ /**
+ * Stores current request context: request, response and locale.
+ */
+ private static final class RequestContext {
+ private static final Pattern ACCEPT_LANGUAGE_SPLIT_PATTERN = Pattern.compile("[,;-]");
+ private static final String LANGUAGE_COOKIE_NAME = "nocturne.language";
+ /**
+ * Http servlet request.
+ */
+ private final HttpServletRequest request;
+
+ /**
+ * Http servlet response.
+ */
+ private final HttpServletResponse response;
+
+ /**
+ * Locale for current request.
+ */
+ private Locale locale;
+
+ /**
+ * Parameters which override request params.
+ */
+ private Map> overrideParameters;
+
+ private RequestContext(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response) {
+ if ((request == null) ^ (response == null)) {
+ logger.error("It is not possible case '(request == null) ^ (response == null)'.");
+ throw new IllegalArgumentException("It is not possible case '(request == null) ^ (response == null)'.");
+ }
+
+ this.request = request;
+ this.response = response;
+
+ if (request == null) {
+ this.locale = null;
+ } else {
+ setupLocale();
+ }
+ }
+
+ /**
+ * @return Http servlet request.
+ */
+ public HttpServletRequest getRequest() {
+ return request;
+ }
+
+ /**
+ * @return Http servlet response.
+ */
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+
+ /**
+ * @return Locale for current request.
+ * Uses lang, language, locale parameters to find
+ * current locale: 2-letter language code.
+ * If it specified once, stores current locale in the session.
+ */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ @Contract(value = "null -> true", pure = true)
+ private static boolean isInvalidLanguage(@Nullable String lang) {
+ return lang == null || lang.length() != 2;
+ }
+
+ private void setupLocale() {
+ Map> requestMap = RequestUtil.getRequestParams(request);
+
+ String lang = RequestUtil.getFirst(requestMap, "lang");
+
+ if (isInvalidLanguage(lang)) {
+ lang = RequestUtil.getFirst(requestMap, "language");
+ }
+
+ if (isInvalidLanguage(lang)) {
+ lang = RequestUtil.getFirst(requestMap, "locale");
+ }
+
+ if (isInvalidLanguage(lang)) {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ lang = (String) session.getAttribute("nocturne.language");
+ }
+
+ if (isInvalidLanguage(lang)) {
+ lang = getCookie(LANGUAGE_COOKIE_NAME);
+ }
+
+ if (isInvalidLanguage(lang)) {
+ lang = getLanguageByGeoIp();
+ if (isInvalidLanguage(lang)) {
+ String[] languages = getAcceptLanguages();
+ for (String language : languages) {
+ if (getInstance().getAllowedLanguages().contains(language)
+ && !"en".equalsIgnoreCase(language)) {
+ lang = language;
+ break;
+ }
+ }
+
+ if (isInvalidLanguage(lang)) {
+ for (String language : languages) {
+ if (getInstance().getAllowedLanguages().contains(language)) {
+ lang = language;
+ break;
+ }
+ }
+ }
+ }
+ }
+ locale = localeByLanguage(lang);
+ } else {
+ locale = localeByLanguage(lang);
+ request.getSession().setAttribute("nocturne.language", locale.getLanguage());
+ addCookie(LANGUAGE_COOKIE_NAME, lang, TimeUnit.DAYS.toSeconds(30));
+ }
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ @Nullable
+ private String getCookie(String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (cookieName.equals(cookie.getName())) {
+ return cookie.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private void addCookie(String cookieName, String cookieValue, long maxAge) {
+ boolean updated = false;
+ if (request.getCookies() != null) {
+ for (Cookie cookie : request.getCookies()) {
+ if (cookie.getName().equals(cookieName)) {
+ cookie.setValue(cookieValue);
+ if (updated) {
+ cookie.setMaxAge(0);
+ } else {
+ cookie.setMaxAge(Ints.checkedCast(maxAge));
+ }
+ cookie.setPath("/");
+ response.addCookie(cookie);
+ updated = true;
+ }
+ }
+ }
+
+ if (!updated) {
+ Cookie cookie = new Cookie(cookieName, cookieValue);
+ cookie.setMaxAge(Ints.checkedCast(maxAge));
+ cookie.setPath("/");
+ response.addCookie(cookie);
+ }
+ }
+
+ @Nullable
+ private String getLanguageByGeoIp() {
+ String countryCode = null; // GeoIpUtil.getCountryCode(request);
+ String lang = getInstance().getCountryToLanguage().get(countryCode);
+ String[] languages = getAcceptLanguages();
+
+ if (ArrayUtils.indexOf(languages, lang) >= 0 && getInstance().getAllowedLanguages().contains(lang)) {
+ return lang;
+ }
+
+ return null;
+ }
+
+ private String[] getAcceptLanguages() {
+ String header = request.getHeader("Accept-Language");
+
+ if (StringUtil.isEmpty(header)) {
+ return EMPTY_STRING_ARRAY;
+ } else {
+ String[] result = ACCEPT_LANGUAGE_SPLIT_PATTERN.split(header);
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = result[i].toLowerCase();
+ }
+ return result;
+ }
+ }
+
+ @Nonnull
+ private static Locale localeByLanguage(@Nullable String language) {
+ if (getInstance().getAllowedLanguages().contains(language)) {
+ return new Locale(Preconditions.checkNotNull(language));
+ } else {
+ return getInstance().getDefaultLocale();
+ }
+ }
+
+ private void addOverrideParameter(String name, String value) {
+ if (overrideParameters == null) {
+ overrideParameters = new HashMap<>();
+ }
+
+ overrideParameters.put(name, new SingleEntryList<>(value));
+ }
+
+ private void addOverrideParameter(String name, Collection values) {
+ if (overrideParameters == null) {
+ overrideParameters = new HashMap<>();
+ }
+
+ overrideParameters.put(name, new ArrayList<>(values));
+ }
+
+ private Map> getOverrideParameters() {
+ return overrideParameters;
+ }
+ }
+}
diff --git a/code/src/main/java/org/nocturne/main/ApplicationContextLoader.java b/code/src/main/java/org/nocturne/main/ApplicationContextLoader.java
index c36d9e4..1c65789 100644
--- a/code/src/main/java/org/nocturne/main/ApplicationContextLoader.java
+++ b/code/src/main/java/org/nocturne/main/ApplicationContextLoader.java
@@ -1,574 +1,574 @@
-/*
- * Copyright 2009 Mike Mirzayanov
- */
-package org.nocturne.main;
-
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
-import org.nocturne.exception.ConfigurationException;
-import org.nocturne.exception.ModuleInitializationException;
-import org.nocturne.exception.NocturneException;
-import org.nocturne.module.Configuration;
-import org.nocturne.module.Module;
-import org.nocturne.prometheus.Prometheus;
-import org.nocturne.reset.ResetStrategy;
-import org.nocturne.reset.annotation.Persist;
-import org.nocturne.reset.annotation.Reset;
-import org.nocturne.util.StringUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * @author Mike Mirzayanov
- */
-class ApplicationContextLoader {
- private static final Logger logger = Logger.getLogger(ApplicationContextLoader.class);
-
- private static final Properties properties = new Properties();
- private static final Pattern ITEMS_SPLIT_PATTERN = Pattern.compile("\\s*;\\s*");
- private static final Pattern LANGUAGES_SPLIT_PATTERN = Pattern.compile("[,;\\s]+");
- private static final Pattern COUNTRIES_TO_LANGUAGE_PATTERN = Pattern.compile("([A-Z]{2},)*[A-Z]{2}:[a-z]{2}");
-
- private static void run() {
- setupDebug();
- setupTemplates();
-
- if (ApplicationContext.getInstance().isDebug()) {
- setupReloadingClassPaths();
- setupClassReloadingPackages();
- setupClassReloadingExceptions();
- setupDebugCaptionsDir();
- setupDebugWebResourcesDir();
- }
-
- setupPageRequestListeners();
- setupGuiceModuleClassName();
- setupSkipRegex();
- setupRequestRouter();
- setupDefaultLocale();
- setupCaptionsImplClass();
- setupCaptionFilesEncoding();
- setupAllowedLanguages();
- setupCountryToLanguage();
- setupDefaultPageClassName();
- setupContextPath();
- setupResetProperties();
- }
-
- private static void setupResetProperties() {
- String strategy = properties.getProperty("nocturne.reset.strategy");
- if (StringUtil.isEmpty(strategy)) {
- ApplicationContext.getInstance().setResetStrategy(ResetStrategy.PERSIST);
- } else {
- ApplicationContext.getInstance().setResetStrategy(ResetStrategy.valueOf(strategy));
- }
-
- String resetAnnotations = properties.getProperty("nocturne.reset.reset-annotations");
- if (StringUtil.isEmpty(resetAnnotations)) {
- ApplicationContext.getInstance().setResetAnnotations(Collections.singletonList(Reset.class.getName()));
- } else {
- String[] annotations = ITEMS_SPLIT_PATTERN.split(resetAnnotations);
- ApplicationContext.getInstance().setResetAnnotations(Arrays.asList(annotations));
- }
-
- String persistAnnotations = properties.getProperty("nocturne.reset.persist-annotations");
- if (StringUtil.isEmpty(persistAnnotations)) {
- ApplicationContext.getInstance().setPersistAnnotations(Arrays.asList(
- Persist.class.getName(),
- Inject.class.getName()
- ));
- } else {
- String[] annotations = ITEMS_SPLIT_PATTERN.split(persistAnnotations);
- ApplicationContext.getInstance().setPersistAnnotations(Arrays.asList(annotations));
- }
- }
-
- private static void setupContextPath() {
- if (properties.containsKey("nocturne.context-path")) {
- String contextPath = properties.getProperty("nocturne.context-path");
- if (contextPath != null) {
- ApplicationContext.getInstance().setContextPath(contextPath);
- }
- }
- }
-
- private static void setupDefaultPageClassName() {
- if (properties.containsKey("nocturne.default-page-class-name")) {
- String className = properties.getProperty("nocturne.default-page-class-name");
- if (className != null && !className.isEmpty()) {
- ApplicationContext.getInstance().setDefaultPageClassName(className);
- }
- }
- }
-
- private static void setupAllowedLanguages() {
- if (properties.containsKey("nocturne.allowed-languages")) {
- String languages = properties.getProperty("nocturne.allowed-languages");
- if (languages != null && !languages.isEmpty()) {
- String[] tokens = LANGUAGES_SPLIT_PATTERN.split(languages);
- List list = new ArrayList<>();
- for (String token : tokens) {
- if (!token.isEmpty()) {
- if (token.length() != 2) {
- logger.error("nocturne.allowed-languages should contain the " +
- "list of 2-letters language codes separated with comma.");
- throw new ConfigurationException("nocturne.allowed-languages should contain the " +
- "list of 2-letters language codes separated with comma.");
- }
- list.add(token);
- }
- }
- ApplicationContext.getInstance().setAllowedLanguages(list);
- }
- }
- }
-
- private static void setupCountryToLanguage() {
- if (properties.containsKey("nocturne.countries-to-language")) {
- String countriesToLanguage = properties.getProperty("nocturne.countries-to-language");
- if (countriesToLanguage != null && !countriesToLanguage.isEmpty()) {
- String[] tokens = ITEMS_SPLIT_PATTERN.split(countriesToLanguage);
- Map result = new HashMap<>();
- for (String token : tokens) {
- if (!token.isEmpty()) {
- if (!COUNTRIES_TO_LANGUAGE_PATTERN.matcher(token).matches()) {
- logger.error("nocturne.countries-to-language should have a form like " +
- "\"RU,BY:ru;EN,GB,US,CA:en\".");
- throw new ConfigurationException("nocturne.countries-to-language should have a form like " +
- "\"RU,BY:ru;EN,GB,US,CA:en\".");
- }
- String[] countriesAndLanguage = token.split(":");
- String[] countries = countriesAndLanguage[0].split(",");
- for (String country : countries) {
- result.put(country, countriesAndLanguage[1]);
- }
- }
- }
- ApplicationContext.getInstance().setCountryToLanguage(result);
- }
- }
- }
-
- private static void setupCaptionFilesEncoding() {
- if (properties.containsKey("nocturne.caption-files-encoding")) {
- String encoding = properties.getProperty("nocturne.caption-files-encoding");
- if (encoding != null && !encoding.isEmpty()) {
- ApplicationContext.getInstance().setCaptionFilesEncoding(encoding);
- }
- }
- }
-
- private static void setupCaptionsImplClass() {
- if (properties.containsKey("nocturne.captions-impl-class")) {
- String clazz = properties.getProperty("nocturne.captions-impl-class");
- if (clazz != null && !clazz.isEmpty()) {
- ApplicationContext.getInstance().setCaptionsImplClass(clazz);
- }
- }
- }
-
- private static void setupDebugCaptionsDir() {
- if (properties.containsKey("nocturne.debug-captions-dir")) {
- String dir = properties.getProperty("nocturne.debug-captions-dir");
- if (dir != null && !dir.isEmpty()) {
- if (!new File(dir).isDirectory() && ApplicationContext.getInstance().isDebug()) {
- logger.error("nocturne.debug-captions-dir property should be a directory.");
- throw new ConfigurationException("nocturne.debug-captions-dir property should be a directory.");
- }
- ApplicationContext.getInstance().setDebugCaptionsDir(dir);
- }
- }
- }
-
- private static void setupDefaultLocale() {
- if (properties.containsKey("nocturne.default-language")) {
- String language = properties.getProperty("nocturne.default-language");
- if (language != null && !language.isEmpty()) {
- if (language.length() != 2) {
- logger.error("Language is expected to have exactly two letters.");
- throw new ConfigurationException("Language is expected to have exactly two letters.");
- }
- ApplicationContext.getInstance().setDefaultLocale(language);
- }
- }
- }
-
- private static void setupRequestRouter() {
- if (properties.containsKey("nocturne.request-router")) {
- String resolver = properties.getProperty("nocturne.request-router");
- if (resolver == null || resolver.isEmpty()) {
- logger.error("Parameter nocturne.request-router can't be empty.");
- throw new ConfigurationException("Parameter nocturne.request-router can't be empty.");
- }
- ApplicationContext.getInstance().setRequestRouter(resolver);
- } else {
- logger.error("Missed parameter nocturne.request-router.");
- throw new ConfigurationException("Missed parameter nocturne.request-router.");
- }
- }
-
- private static void setupDebugWebResourcesDir() {
- if (properties.containsKey("nocturne.debug-web-resources-dir")) {
- String dir = properties.getProperty("nocturne.debug-web-resources-dir");
- if (dir != null && !dir.trim().isEmpty()) {
- ApplicationContext.getInstance().setDebugWebResourcesDir(dir.trim());
- }
- }
- }
-
- private static void setupClassReloadingExceptions() {
- List exceptions = new ArrayList<>();
- exceptions.add(ApplicationContext.class.getName());
- exceptions.add(Prometheus.class.getName());
- if (properties.containsKey("nocturne.class-reloading-exceptions")) {
- String exceptionsAsString = properties.getProperty("nocturne.class-reloading-exceptions");
- if (exceptionsAsString != null) {
- exceptions.addAll(listOfNonEmpties(ITEMS_SPLIT_PATTERN.split(exceptionsAsString)));
- }
- }
- ApplicationContext.getInstance().setClassReloadingExceptions(exceptions);
- }
-
- private static void setupClassReloadingPackages() {
- List packages = new ArrayList<>();
- packages.add("org.nocturne");
-
- if (properties.containsKey("nocturne.class-reloading-packages")) {
- String packagesAsString = properties.getProperty("nocturne.class-reloading-packages");
- if (packagesAsString != null) {
- packages.addAll(listOfNonEmpties(ITEMS_SPLIT_PATTERN.split(packagesAsString)));
- }
- }
- ApplicationContext.getInstance().setClassReloadingPackages(packages);
- }
-
- private static void setupSkipRegex() {
- if (properties.containsKey("nocturne.skip-regex")) {
- String regex = properties.getProperty("nocturne.skip-regex");
- if (regex != null && !regex.isEmpty()) {
- try {
- ApplicationContext.getInstance().setSkipRegex(Pattern.compile(regex));
- } catch (PatternSyntaxException e) {
- logger.error("Parameter nocturne.skip-regex contains invalid pattern.", e);
- throw new ConfigurationException("Parameter nocturne.skip-regex contains invalid pattern.", e);
- }
- }
- }
- }
-
- private static void setupGuiceModuleClassName() {
- if (properties.containsKey("nocturne.guice-module-class-name")) {
- String module = properties.getProperty("nocturne.guice-module-class-name");
- if (module != null && !module.isEmpty()) {
- ApplicationContext.getInstance().setGuiceModuleClassName(module);
- }
- }
- }
-
- private static void setupPageRequestListeners() {
- List listeners = new ArrayList<>();
- if (properties.containsKey("nocturne.page-request-listeners")) {
- String pageRequestListenersAsString = properties.getProperty("nocturne.page-request-listeners");
- if (pageRequestListenersAsString != null) {
- listeners.addAll(listOfNonEmpties(ITEMS_SPLIT_PATTERN.split(pageRequestListenersAsString)));
- }
- }
- ApplicationContext.getInstance().setPageRequestListeners(listeners);
- }
-
- private static void setupReloadingClassPaths() {
- List reloadingClassPaths = new ArrayList<>();
- if (properties.containsKey("nocturne.reloading-class-paths")) {
- String reloadingClassPathsAsString = properties.getProperty("nocturne.reloading-class-paths");
- if (reloadingClassPathsAsString != null) {
- String[] dirs = ITEMS_SPLIT_PATTERN.split(reloadingClassPathsAsString);
- for (String dir : dirs) {
- if (dir != null && !dir.isEmpty()) {
- File file = new File(dir);
- if (!file.isDirectory() && ApplicationContext.getInstance().isDebug()) {
- logger.error("Each item in nocturne.reloading-class-paths should be a directory,"
- + " but " + file + " is not.");
- throw new ConfigurationException("Each item in nocturne.reloading-class-paths should be a directory,"
- + " but " + file + " is not.");
- }
- reloadingClassPaths.add(file);
- }
- }
- }
- }
- ApplicationContext.getInstance().setReloadingClassPaths(reloadingClassPaths);
- }
-
- private static void setupTemplates() {
- if (properties.containsKey("nocturne.templates-update-delay")) {
- try {
- int templatesUpdateDelay = Integer.parseInt(properties.getProperty("nocturne.templates-update-delay"));
- if (templatesUpdateDelay < 0 || templatesUpdateDelay > 86400) {
- logger.error("Parameter nocturne.templates-update-delay should be non-negative integer not greater than 86400.");
- throw new ConfigurationException("Parameter nocturne.templates-update-delay should be non-negative integer not greater than 86400.");
- }
- ApplicationContext.getInstance().setTemplatesUpdateDelay(templatesUpdateDelay);
- } catch (NumberFormatException e) {
- logger.error("Parameter nocturne.templates-update-delay should be integer.", e);
- throw new ConfigurationException("Parameter nocturne.templates-update-delay should be integer.", e);
- }
- }
-
- if (properties.containsKey("nocturne.template-paths")) {
- String[] templatePaths = ITEMS_SPLIT_PATTERN.split(StringUtils.trimToEmpty(
- properties.getProperty("nocturne.template-paths")
- ));
-
- for (String templatePath : templatePaths) {
- if (templatePath.isEmpty()) {
- logger.error("Item of parameter nocturne.template-paths can't be empty.");
- throw new ConfigurationException("Item of parameter nocturne.template-paths can't be empty.");
- }
- }
- ApplicationContext.getInstance().setTemplatePaths(templatePaths);
- } else if (properties.containsKey("nocturne.templates-path")) {
- logger.warn("Property nocturne.templates-path is deprecated. Use semicolon separated nocturne.template-paths.");
-
- String templatesPath = StringUtils.trimToEmpty(properties.getProperty("nocturne.templates-path"));
- if (templatesPath.isEmpty()) {
- logger.error("Parameter nocturne.templates-path can't be empty.");
- throw new ConfigurationException("Parameter nocturne.templates-path can't be empty.");
- }
- ApplicationContext.getInstance().setTemplatePaths(new String[]{templatesPath});
- } else {
- logger.error("Missing parameter nocturne.template-paths.");
- throw new ConfigurationException("Missing parameter nocturne.template-paths.");
- }
-
- if (properties.containsKey("nocturne.sticky-template-paths")) {
- String stickyTemplatePaths = StringUtils.trimToEmpty(properties.getProperty("nocturne.sticky-template-paths"));
- if (!stickyTemplatePaths.isEmpty()) {
- ApplicationContext.getInstance().setStickyTemplatePaths(Boolean.parseBoolean(stickyTemplatePaths));
- }
- }
-
- if (properties.containsKey("nocturne.use-component-templates")) {
- String useComponentTemplates = properties.getProperty("nocturne.use-component-templates");
- if (!"false".equals(useComponentTemplates) && !"true".equals(useComponentTemplates)) {
- logger.error("Parameter nocturne.use-component-templates expected to be 'false' or 'true'.");
- throw new ConfigurationException("Parameter nocturne.use-component-templates expected to be 'false' or 'true'.");
- }
- boolean use = "true".equals(useComponentTemplates);
- ApplicationContext.getInstance().setUseComponentTemplates(use);
- if (use && properties.containsKey("nocturne.component-templates-less-commons-file")) {
- String componentTemplatesLessCommonsFileAsString
- = properties.getProperty("nocturne.component-templates-less-commons-file");
- if (!StringUtil.isBlank(componentTemplatesLessCommonsFileAsString)) {
- File componentTemplatesLessCommonsFile = new File(componentTemplatesLessCommonsFileAsString);
- if (componentTemplatesLessCommonsFile.isFile()) {
- ApplicationContext.getInstance().setComponentTemplatesLessCommonsFile(componentTemplatesLessCommonsFile);
- } else {
- logger.error("Parameter nocturne.component-templates-less-commons-file is expected to be a file.");
- throw new ConfigurationException("Parameter nocturne.component-templates-less-commons-file is expected to be a file.");
- }
- }
- }
- }
- }
-
- private static void setupDebug() {
- ApplicationContext.getInstance().setDebug(Boolean.parseBoolean(properties.getProperty("nocturne.debug")));
- }
-
- private static List