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}}
- *      {@literal <@caption>Login}
- *      {@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}}
+ *      {@literal <@caption>Login}
+ *      {@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 collection) { - boolean changed = false; - - for (E e : collection) { - changed |= add(e); - } - - return changed; - } - - @Override - public boolean addAll(int index, @Nonnull Collection 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 collection) { + boolean changed = false; + + for (E e : collection) { + changed |= add(e); + } + + return changed; + } + + @Override + public boolean addAll(int index, @Nonnull Collection 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("\n

Please 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("\n

Please 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[] 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[] 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[] types() { - return types; - } - - @Override - public Class 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[] 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[] 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[] types() { + return types; + } + + @Override + public Class 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 clazz = (Class) 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 clazz = (Class) 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 listOfNonEmpties(String[] strings) { - List result = new ArrayList<>(strings.length); - for (String s : strings) { - if (!StringUtil.isEmpty(s)) { - result.add(s); - } - } - return result; - } - - /** - * Scans classpath for modules. - * - * @return List of modules ordered by priority (from high priority to low). - */ - private static List getModulesFromClasspath() { - List modules = new ArrayList<>(); - URLClassLoader loader = (URLClassLoader) ApplicationContext.class.getClassLoader(); - URL[] classPath = loader.getURLs(); - for (URL url : classPath) { - if (Module.isModuleUrl(url)) { - modules.add(new Module(url)); - } - } - return modules; - } - - /** - * Runs init() method for all modules. - * Each module should be initialized on the application startup. - */ - private static void initializeModules() { - List modules = getModulesFromClasspath(); - - for (Module module : modules) { - module.init(); - } - - modules.sort((moduleA, moduleB) -> { - int priorityComparisonResult = Integer.compare(moduleB.getPriority(), moduleA.getPriority()); - if (priorityComparisonResult != 0) { - return priorityComparisonResult; - } - - return moduleA.getName().compareTo(moduleB.getName()); - }); - - for (Module module : modules) { - module.getConfiguration().addPages(); - } - - ApplicationContext.getInstance().setModules(modules); - } - - private static void setupInjector() { - String guiceModuleClassName = ApplicationContext.getInstance().getGuiceModuleClassName(); - GenericIocModule module = new GenericIocModule(); - - if (!StringUtil.isEmpty(guiceModuleClassName)) { - try { - module.setModule(getApplicationModule(guiceModuleClassName)); - } catch (Exception e) { - logger.error("Can't load application Guice module.", e); - throw new ConfigurationException("Can't load application Guice module.", e); - } - } - - Injector injector = Guice.createInjector(module); - - if (ApplicationContext.getInstance().isDebug()) { - try { - Method method = ApplicationContext.class.getDeclaredMethod("setInjector", Injector.class); - method.setAccessible(true); - method.invoke(ApplicationContext.getInstance(), injector); - } catch (NoSuchMethodException e) { - logger.error("Can't find method setInjector.", e); - throw new NocturneException("Can't find method setInjector.", e); - } catch (InvocationTargetException e) { - logger.error("InvocationTargetException", e); - throw new NocturneException("InvocationTargetException", e); - } catch (IllegalAccessException e) { - logger.error("IllegalAccessException", e); - throw new NocturneException("IllegalAccessException", e); - } - } else { - ApplicationContext.getInstance().setInjector(injector); - } - } - - private static com.google.inject.Module getApplicationModule(String guiceModuleClassName) throws Exception { - Class moduleClass = ApplicationContext.class.getClassLoader().loadClass(guiceModuleClassName); - AtomicReference exception = new AtomicReference<>(); - - try { - return (com.google.inject.Module) moduleClass.getConstructor().newInstance(); - } catch (Exception e) { - exception.compareAndSet(null, e); - } - - try { - Method getInstanceMethod = moduleClass.getMethod("getInstance"); - if (Modifier.isStatic(getInstanceMethod.getModifiers()) - && com.google.inject.Module.class.isAssignableFrom(getInstanceMethod.getReturnType())) { - return (com.google.inject.Module) getInstanceMethod.invoke(null); - } - } catch (Exception e) { - exception.compareAndSet(null, e); - } - - try { - Method createInstanceMethod = moduleClass.getMethod("createInstance"); - if (Modifier.isStatic(createInstanceMethod.getModifiers()) - && com.google.inject.Module.class.isAssignableFrom(createInstanceMethod.getReturnType())) { - return (com.google.inject.Module) createInstanceMethod.invoke(null); - } - } catch (Exception e) { - exception.compareAndSet(null, e); - } - - try { - Method newInstanceMethod = moduleClass.getMethod("newInstance"); - if (Modifier.isStatic(newInstanceMethod.getModifiers()) - && com.google.inject.Module.class.isAssignableFrom(newInstanceMethod.getReturnType())) { - return (com.google.inject.Module) newInstanceMethod.invoke(null); - } - } catch (Exception e) { - exception.compareAndSet(null, e); - } - - throw exception.get(); - } - - private static void runModuleStartups() { - List modules = ApplicationContext.getInstance().getModules(); - for (Module module : modules) { - String startupClassName = module.getStartupClassName(); - if (!startupClassName.isEmpty()) { - Runnable runnable; - try { - runnable = (Runnable) ApplicationContext.getInstance().getInjector().getInstance( - ApplicationContext.class.getClassLoader().loadClass(startupClassName)); - } catch (ClassCastException e) { - logger.error("Startup class " + startupClassName + " must implement Runnable.", e); - throw new ModuleInitializationException("Startup class " + startupClassName - + " must implement Runnable.", e); - } catch (ClassNotFoundException e) { - logger.error("Can't load startup class be name " + startupClassName + '.', e); - throw new ModuleInitializationException("Can't load startup class be name " - + startupClassName + '.', e); - } - if (runnable != null) { - runnable.run(); - } - } - } - } - - static void initialize() { - synchronized (ApplicationContextLoader.class) { - run(); - initializeModules(); - setupInjector(); - runModuleStartups(); - ApplicationContext.getInstance().setInitialized(); - } - } - - static void shutdown() { - synchronized (ApplicationContextLoader.class) { - ApplicationContext.getInstance().getModules().parallelStream() - .map(Module::getConfiguration).filter(Objects::nonNull).forEach(Configuration::shutdown); - } - } - - static { - try (InputStream inputStream = ApplicationContextLoader.class.getResourceAsStream(Constants.CONFIGURATION_FILE)) { - properties.load(inputStream); - } catch (IOException e) { - logger.error("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); - throw new ConfigurationException("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); - } - } -} +/* + * 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 listOfNonEmpties(String[] strings) { + List result = new ArrayList<>(strings.length); + for (String s : strings) { + if (!StringUtil.isEmpty(s)) { + result.add(s); + } + } + return result; + } + + /** + * Scans classpath for modules. + * + * @return List of modules ordered by priority (from high priority to low). + */ + private static List getModulesFromClasspath() { + List modules = new ArrayList<>(); + URLClassLoader loader = (URLClassLoader) ApplicationContext.class.getClassLoader(); + URL[] classPath = loader.getURLs(); + for (URL url : classPath) { + if (Module.isModuleUrl(url)) { + modules.add(new Module(url)); + } + } + return modules; + } + + /** + * Runs init() method for all modules. + * Each module should be initialized on the application startup. + */ + private static void initializeModules() { + List modules = getModulesFromClasspath(); + + for (Module module : modules) { + module.init(); + } + + modules.sort((moduleA, moduleB) -> { + int priorityComparisonResult = Integer.compare(moduleB.getPriority(), moduleA.getPriority()); + if (priorityComparisonResult != 0) { + return priorityComparisonResult; + } + + return moduleA.getName().compareTo(moduleB.getName()); + }); + + for (Module module : modules) { + module.getConfiguration().addPages(); + } + + ApplicationContext.getInstance().setModules(modules); + } + + private static void setupInjector() { + String guiceModuleClassName = ApplicationContext.getInstance().getGuiceModuleClassName(); + GenericIocModule module = new GenericIocModule(); + + if (!StringUtil.isEmpty(guiceModuleClassName)) { + try { + module.setModule(getApplicationModule(guiceModuleClassName)); + } catch (Exception e) { + logger.error("Can't load application Guice module.", e); + throw new ConfigurationException("Can't load application Guice module.", e); + } + } + + Injector injector = Guice.createInjector(module); + + if (ApplicationContext.getInstance().isDebug()) { + try { + Method method = ApplicationContext.class.getDeclaredMethod("setInjector", Injector.class); + method.setAccessible(true); + method.invoke(ApplicationContext.getInstance(), injector); + } catch (NoSuchMethodException e) { + logger.error("Can't find method setInjector.", e); + throw new NocturneException("Can't find method setInjector.", e); + } catch (InvocationTargetException e) { + logger.error("InvocationTargetException", e); + throw new NocturneException("InvocationTargetException", e); + } catch (IllegalAccessException e) { + logger.error("IllegalAccessException", e); + throw new NocturneException("IllegalAccessException", e); + } + } else { + ApplicationContext.getInstance().setInjector(injector); + } + } + + private static com.google.inject.Module getApplicationModule(String guiceModuleClassName) throws Exception { + Class moduleClass = ApplicationContext.class.getClassLoader().loadClass(guiceModuleClassName); + AtomicReference exception = new AtomicReference<>(); + + try { + return (com.google.inject.Module) moduleClass.getConstructor().newInstance(); + } catch (Exception e) { + exception.compareAndSet(null, e); + } + + try { + Method getInstanceMethod = moduleClass.getMethod("getInstance"); + if (Modifier.isStatic(getInstanceMethod.getModifiers()) + && com.google.inject.Module.class.isAssignableFrom(getInstanceMethod.getReturnType())) { + return (com.google.inject.Module) getInstanceMethod.invoke(null); + } + } catch (Exception e) { + exception.compareAndSet(null, e); + } + + try { + Method createInstanceMethod = moduleClass.getMethod("createInstance"); + if (Modifier.isStatic(createInstanceMethod.getModifiers()) + && com.google.inject.Module.class.isAssignableFrom(createInstanceMethod.getReturnType())) { + return (com.google.inject.Module) createInstanceMethod.invoke(null); + } + } catch (Exception e) { + exception.compareAndSet(null, e); + } + + try { + Method newInstanceMethod = moduleClass.getMethod("newInstance"); + if (Modifier.isStatic(newInstanceMethod.getModifiers()) + && com.google.inject.Module.class.isAssignableFrom(newInstanceMethod.getReturnType())) { + return (com.google.inject.Module) newInstanceMethod.invoke(null); + } + } catch (Exception e) { + exception.compareAndSet(null, e); + } + + throw exception.get(); + } + + private static void runModuleStartups() { + List modules = ApplicationContext.getInstance().getModules(); + for (Module module : modules) { + String startupClassName = module.getStartupClassName(); + if (!startupClassName.isEmpty()) { + Runnable runnable; + try { + runnable = (Runnable) ApplicationContext.getInstance().getInjector().getInstance( + ApplicationContext.class.getClassLoader().loadClass(startupClassName)); + } catch (ClassCastException e) { + logger.error("Startup class " + startupClassName + " must implement Runnable.", e); + throw new ModuleInitializationException("Startup class " + startupClassName + + " must implement Runnable.", e); + } catch (ClassNotFoundException e) { + logger.error("Can't load startup class be name " + startupClassName + '.', e); + throw new ModuleInitializationException("Can't load startup class be name " + + startupClassName + '.', e); + } + if (runnable != null) { + runnable.run(); + } + } + } + } + + static void initialize() { + synchronized (ApplicationContextLoader.class) { + run(); + initializeModules(); + setupInjector(); + runModuleStartups(); + ApplicationContext.getInstance().setInitialized(); + } + } + + static void shutdown() { + synchronized (ApplicationContextLoader.class) { + ApplicationContext.getInstance().getModules().parallelStream() + .map(Module::getConfiguration).filter(Objects::nonNull).forEach(Configuration::shutdown); + } + } + + static { + try (InputStream inputStream = ApplicationContextLoader.class.getResourceAsStream(Constants.CONFIGURATION_FILE)) { + properties.load(inputStream); + } catch (IOException e) { + logger.error("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); + throw new ConfigurationException("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/ApplicationTemplateLoader.java b/code/src/main/java/org/nocturne/main/ApplicationTemplateLoader.java index 2d2b735..c884678 100644 --- a/code/src/main/java/org/nocturne/main/ApplicationTemplateLoader.java +++ b/code/src/main/java/org/nocturne/main/ApplicationTemplateLoader.java @@ -1,132 +1,132 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import freemarker.cache.TemplateLoader; -import org.apache.log4j.Logger; -import org.nocturne.exception.NocturneException; -import org.nocturne.module.Module; -import org.nocturne.module.PreprocessFreemarkerFileTemplateLoader; -import org.nocturne.util.FileUtil; - -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * This template loader will delegate all the requests to - * standard FileTemplateLoader in production mode or - * loads template from modules DebugContext in debug mode. - */ -public class ApplicationTemplateLoader implements TemplateLoader { - /** - * Logger. - */ - private static final Logger logger = Logger.getLogger(ApplicationTemplateLoader.class); - - /** - * List of loaded modules. - */ - private final List modules; - - /** - * Instance of ApplicationContext - just shortcut. - */ - private final ApplicationContext applicationContext = ApplicationContext.getInstance(); - - /** - * For debug mode stores loader by loaded object. - */ - private final Map loadersByTemplate = new WeakHashMap<>(); - - /** - * Usual file template loader, uses nocturne.templates-path. - */ - private final TemplateLoader templateLoader; - - /** - * New ApplicationTemplateLoader. - */ - public ApplicationTemplateLoader() { - modules = applicationContext.getModules(); - - String[] templatePaths = applicationContext.getTemplatePaths(); - int templateDirCount = templatePaths.length; - File[] templateDirs = new File[templateDirCount]; - - for (int dirIndex = 0; dirIndex < templateDirCount; ++dirIndex) { - String templatePath = templatePaths[dirIndex]; - File templatePathFile = new File(templatePath); - - if (!templatePathFile.isAbsolute() || !templatePathFile.exists()) { - String realTemplatePath = FileUtil.getRealPath(applicationContext.getServletContext(), templatePath); - if (realTemplatePath == null) { - throw new NocturneException("Can't find '" + templatePath + "' in servletContext."); - } else { - templatePath = realTemplatePath; - } - } - - templatePathFile = new File(templatePath); - if (!templatePathFile.exists()) { - throw new NocturneException("Can't find template path '" + templatePath + "' in servletContext."); - } - - templateDirs[dirIndex] = templatePathFile; - } - - try { - templateLoader = new PreprocessFreemarkerFileTemplateLoader(templateDirs); - } catch (IOException e) { - throw new NocturneException("Can't create FileTemplateLoader for delegation.", e); - } - } - - @Override - public Object findTemplateSource(String s) throws IOException { - if (applicationContext.isDebug()) { - for (Module module : modules) { - Object result = module.getTemplateLoader().findTemplateSource(s); - if (result != null) { - loadersByTemplate.put(result, module.getTemplateLoader()); - return result; - } - } - } - return templateLoader.findTemplateSource(s); - } - - @Override - public long getLastModified(Object o) { - if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { - return loadersByTemplate.get(o).getLastModified(o); - } - return templateLoader.getLastModified(o); - } - - @Override - public Reader getReader(Object o, String s) throws IOException { - if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { - return loadersByTemplate.get(o).getReader(o, s); - } - - return templateLoader.getReader(o, s); - } - - @Override - public void closeTemplateSource(Object o) throws IOException { - if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { - TemplateLoader loader = loadersByTemplate.get(o); - if (loader != null) { - loader.closeTemplateSource(o); - loadersByTemplate.remove(o); - } - } - - templateLoader.closeTemplateSource(o); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import freemarker.cache.TemplateLoader; +import org.apache.log4j.Logger; +import org.nocturne.exception.NocturneException; +import org.nocturne.module.Module; +import org.nocturne.module.PreprocessFreemarkerFileTemplateLoader; +import org.nocturne.util.FileUtil; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * This template loader will delegate all the requests to + * standard FileTemplateLoader in production mode or + * loads template from modules DebugContext in debug mode. + */ +public class ApplicationTemplateLoader implements TemplateLoader { + /** + * Logger. + */ + private static final Logger logger = Logger.getLogger(ApplicationTemplateLoader.class); + + /** + * List of loaded modules. + */ + private final List modules; + + /** + * Instance of ApplicationContext - just shortcut. + */ + private final ApplicationContext applicationContext = ApplicationContext.getInstance(); + + /** + * For debug mode stores loader by loaded object. + */ + private final Map loadersByTemplate = new WeakHashMap<>(); + + /** + * Usual file template loader, uses nocturne.templates-path. + */ + private final TemplateLoader templateLoader; + + /** + * New ApplicationTemplateLoader. + */ + public ApplicationTemplateLoader() { + modules = applicationContext.getModules(); + + String[] templatePaths = applicationContext.getTemplatePaths(); + int templateDirCount = templatePaths.length; + File[] templateDirs = new File[templateDirCount]; + + for (int dirIndex = 0; dirIndex < templateDirCount; ++dirIndex) { + String templatePath = templatePaths[dirIndex]; + File templatePathFile = new File(templatePath); + + if (!templatePathFile.isAbsolute() || !templatePathFile.exists()) { + String realTemplatePath = FileUtil.getRealPath(applicationContext.getServletContext(), templatePath); + if (realTemplatePath == null) { + throw new NocturneException("Can't find '" + templatePath + "' in servletContext."); + } else { + templatePath = realTemplatePath; + } + } + + templatePathFile = new File(templatePath); + if (!templatePathFile.exists()) { + throw new NocturneException("Can't find template path '" + templatePath + "' in servletContext."); + } + + templateDirs[dirIndex] = templatePathFile; + } + + try { + templateLoader = new PreprocessFreemarkerFileTemplateLoader(templateDirs); + } catch (IOException e) { + throw new NocturneException("Can't create FileTemplateLoader for delegation.", e); + } + } + + @Override + public Object findTemplateSource(String s) throws IOException { + if (applicationContext.isDebug()) { + for (Module module : modules) { + Object result = module.getTemplateLoader().findTemplateSource(s); + if (result != null) { + loadersByTemplate.put(result, module.getTemplateLoader()); + return result; + } + } + } + return templateLoader.findTemplateSource(s); + } + + @Override + public long getLastModified(Object o) { + if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { + return loadersByTemplate.get(o).getLastModified(o); + } + return templateLoader.getLastModified(o); + } + + @Override + public Reader getReader(Object o, String s) throws IOException { + if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { + return loadersByTemplate.get(o).getReader(o, s); + } + + return templateLoader.getReader(o, s); + } + + @Override + public void closeTemplateSource(Object o) throws IOException { + if (applicationContext.isDebug() && loadersByTemplate.containsKey(o)) { + TemplateLoader loader = loadersByTemplate.get(o); + if (loader != null) { + loader.closeTemplateSource(o); + loadersByTemplate.remove(o); + } + } + + templateLoader.closeTemplateSource(o); + } +} diff --git a/code/src/main/java/org/nocturne/main/Constants.java b/code/src/main/java/org/nocturne/main/Constants.java index 81e8ab9..edc20c7 100644 --- a/code/src/main/java/org/nocturne/main/Constants.java +++ b/code/src/main/java/org/nocturne/main/Constants.java @@ -1,18 +1,18 @@ -package org.nocturne.main; - -import freemarker.template.Configuration; -import freemarker.template.Version; - -/** - * @author Maxim Shipko (sladethe@gmail.com) - * Date: 10.02.15 - */ -@SuppressWarnings("WeakerAccess") -public class Constants { - public static final String CONFIGURATION_FILE = "/nocturne.properties"; - public static final Version FREEMARKER_VERSION = Configuration.VERSION_2_3_21; - - private Constants() { - throw new UnsupportedOperationException(); - } -} +package org.nocturne.main; + +import freemarker.template.Configuration; +import freemarker.template.Version; + +/** + * @author Maxim Shipko (sladethe@gmail.com) + * Date: 10.02.15 + */ +@SuppressWarnings("WeakerAccess") +public class Constants { + public static final String CONFIGURATION_FILE = "/nocturne.properties"; + public static final Version FREEMARKER_VERSION = Configuration.VERSION_2_3_21; + + private Constants() { + throw new UnsupportedOperationException(); + } +} diff --git a/code/src/main/java/org/nocturne/main/DebugResourceFilter.java b/code/src/main/java/org/nocturne/main/DebugResourceFilter.java index 5456945..9acf86c 100644 --- a/code/src/main/java/org/nocturne/main/DebugResourceFilter.java +++ b/code/src/main/java/org/nocturne/main/DebugResourceFilter.java @@ -1,215 +1,215 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import eu.medsea.mimeutil.MimeType; -import eu.medsea.mimeutil.detector.ExtensionMimeDetector; -import org.apache.log4j.Logger; -import org.jetbrains.annotations.Contract; -import org.nocturne.exception.NocturneException; -import org.nocturne.exception.ReflectionException; -import org.nocturne.module.Module; -import org.nocturne.util.ReflectionUtil; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * You may use this filter only for debug purpose. - * Use it for css, js and image resources. It loads - * resources from modules and if you change - * resources in IDE it will load renewed version. - */ -@SuppressWarnings({"unused"}) -public class DebugResourceFilter implements Filter { - private static final Logger logger = Logger.getLogger(DebugResourceFilter.class); - - static { - String mimeDetectorName = ExtensionMimeDetector.class.getName(); - if (eu.medsea.mimeutil.MimeUtil.getMimeDetector(mimeDetectorName) == null) { - eu.medsea.mimeutil.MimeUtil.registerMimeDetector(mimeDetectorName); - } - } - - @SuppressWarnings("RedundantThrows") - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // No operations. - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (ReloadingContext.getInstance().isDebug()) { - DispatchFilter.updateRequestDispatcher(); - - if (getClass().getClassLoader() == DispatchFilter.lastReloadingClassLoader) { - handleDebugModeDoFilter(request, response, chain); - } else { - Object object; - - try { - object = DispatchFilter.lastReloadingClassLoader.loadClass(DebugResourceFilter.class.getName()).getConstructor().newInstance(); - } catch (Exception e) { - logger.error("Can't create instance of DebugResourceFilter.", e); - throw new NocturneException("Can't create instance of DebugResourceFilter.", e); - } - - try { - ReflectionUtil.invoke(object, "handleDebugModeDoFilter", request, response, chain); - } catch (ReflectionException e) { - logger.error("Can't run DebugResourceFilter.", e); - throw new NocturneException("Can't run DebugResourceFilter.", e); - } - } - } else { - chain.doFilter(request, response); - } - } - - private static void handleDebugModeDoFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String path = httpRequest.getServletPath(); - - List modules = ApplicationContext.getInstance().getModules(); - for (Module module : modules) { - if (processModuleResource(module, path, response)) { - return; - } - } - - String resourcesDir = ApplicationContext.getInstance().getDebugWebResourcesDir(); - if (resourcesDir != null) { - File resourceFile = new File(resourcesDir, path); - if (resourceFile.isFile()) { - InputStream resourceInputStream = new FileInputStream(resourceFile); - writeResourceByPathAndStream(response, path, resourceInputStream); - return; - } - } - - filterChain.doFilter(request, response); - } - } - - private static boolean processModuleResource(Module module, String path, ServletResponse response) throws IOException { - InputStream inputStream = module.getResourceLoader().getResourceInputStream(path); - return writeResourceByPathAndStream(response, path, inputStream); - } - - private static boolean writeResourceByPathAndStream(ServletResponse response, String path, InputStream inputStream) throws IOException { - if (inputStream != null) { - try (OutputStream outputStream = response.getOutputStream()) { - setupContentType(path, response); - - int size = 0; - byte[] buffer = new byte[65536]; - - while (true) { - int readCount = inputStream.read(buffer); - - if (readCount >= 0) { - outputStream.write(buffer, 0, readCount); - size += readCount; - } else { - break; - } - } - response.setContentLength(size); - } finally { - inputStream.close(); - } - - return true; - } else { - return false; - } - } - - @Contract("null, _ -> fail") - private static void setupContentType(String path, ServletResponse response) { - if (path != null) { - String mimeType = MimeUtil.getMimeType(path); - - if (mimeType != null) { - response.setContentType(mimeType); - return; - } - } - - throw new org.nocturne.exception.ServletException("Can't set content type for " + path + '.'); - } - - @Override - public void destroy() { - } - - private static final class MimeUtil { - private static final Map mimeTypeByExtension = new ConcurrentHashMap<>(); - - private static void add(String mimeType, String... extensions) { - for (String extension : extensions) { - if (mimeTypeByExtension.containsKey(extension)) { - throw new NocturneException("Already has registered mime type by " + extension + '.'); - } - mimeTypeByExtension.put(extension, mimeType); - } - } - - private static String getMimeType(String path) { - String extension = (path.indexOf('.') < 0 ? path : path.substring(path.lastIndexOf('.') + 1)).toLowerCase(); - String result = mimeTypeByExtension.get(extension); - if (result != null) { - return result; - } - - MimeType mimeType = eu.medsea.mimeutil.MimeUtil.getMostSpecificMimeType(eu.medsea.mimeutil.MimeUtil.getMimeTypes(new File(path).getName())); - if (mimeType != null) { - return mimeType.toString(); - } - - return "application/octet-stream"; - } - - static { - add("application/wasm", "wasm"); - add("application/json", "json"); - add("application/javascript", "js"); - add("application/pdf", "pdf"); - add("application/postscript", "ps"); - add("application/font-woff", "woff"); - add("application/xhtml+xml", "xhtml"); - add("application/xml-dtd", "dtd"); - add("application/zip", "zip"); - add("application/gzip", "gzip"); - add("application/x-tex", "tex"); - add("application/xml", "xml"); - add("audio/aac", "acc"); - add("audio/mpeg", "mp3"); - add("audio/ogg", "ogg"); - add("image/gif", "gif"); - add("image/jpeg", "jpeg"); - add("image/png", "png"); - add("image/svg+xml", "svg"); - add("image/tiff", "tiff"); - add("image/webp", "webp"); - add("image/bmp", "bmp"); - add("text/plain", "txt"); - add("text/css", "css"); - add("text/html", "html", "htm"); - add("text/x-java-source", "java"); - add("text/x-c", "cpp"); - add("text/x-c", "c"); - add("video/avi", "avi"); - add("video/mp4", "mp4"); - add("video/mpeg", "mpeg"); - } - } - -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import eu.medsea.mimeutil.MimeType; +import eu.medsea.mimeutil.detector.ExtensionMimeDetector; +import org.apache.log4j.Logger; +import org.jetbrains.annotations.Contract; +import org.nocturne.exception.NocturneException; +import org.nocturne.exception.ReflectionException; +import org.nocturne.module.Module; +import org.nocturne.util.ReflectionUtil; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * You may use this filter only for debug purpose. + * Use it for css, js and image resources. It loads + * resources from modules and if you change + * resources in IDE it will load renewed version. + */ +@SuppressWarnings({"unused"}) +public class DebugResourceFilter implements Filter { + private static final Logger logger = Logger.getLogger(DebugResourceFilter.class); + + static { + String mimeDetectorName = ExtensionMimeDetector.class.getName(); + if (eu.medsea.mimeutil.MimeUtil.getMimeDetector(mimeDetectorName) == null) { + eu.medsea.mimeutil.MimeUtil.registerMimeDetector(mimeDetectorName); + } + } + + @SuppressWarnings("RedundantThrows") + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // No operations. + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (ReloadingContext.getInstance().isDebug()) { + DispatchFilter.updateRequestDispatcher(); + + if (getClass().getClassLoader() == DispatchFilter.lastReloadingClassLoader) { + handleDebugModeDoFilter(request, response, chain); + } else { + Object object; + + try { + object = DispatchFilter.lastReloadingClassLoader.loadClass(DebugResourceFilter.class.getName()).getConstructor().newInstance(); + } catch (Exception e) { + logger.error("Can't create instance of DebugResourceFilter.", e); + throw new NocturneException("Can't create instance of DebugResourceFilter.", e); + } + + try { + ReflectionUtil.invoke(object, "handleDebugModeDoFilter", request, response, chain); + } catch (ReflectionException e) { + logger.error("Can't run DebugResourceFilter.", e); + throw new NocturneException("Can't run DebugResourceFilter.", e); + } + } + } else { + chain.doFilter(request, response); + } + } + + private static void handleDebugModeDoFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { + if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String path = httpRequest.getServletPath(); + + List modules = ApplicationContext.getInstance().getModules(); + for (Module module : modules) { + if (processModuleResource(module, path, response)) { + return; + } + } + + String resourcesDir = ApplicationContext.getInstance().getDebugWebResourcesDir(); + if (resourcesDir != null) { + File resourceFile = new File(resourcesDir, path); + if (resourceFile.isFile()) { + InputStream resourceInputStream = new FileInputStream(resourceFile); + writeResourceByPathAndStream(response, path, resourceInputStream); + return; + } + } + + filterChain.doFilter(request, response); + } + } + + private static boolean processModuleResource(Module module, String path, ServletResponse response) throws IOException { + InputStream inputStream = module.getResourceLoader().getResourceInputStream(path); + return writeResourceByPathAndStream(response, path, inputStream); + } + + private static boolean writeResourceByPathAndStream(ServletResponse response, String path, InputStream inputStream) throws IOException { + if (inputStream != null) { + try (OutputStream outputStream = response.getOutputStream()) { + setupContentType(path, response); + + int size = 0; + byte[] buffer = new byte[65536]; + + while (true) { + int readCount = inputStream.read(buffer); + + if (readCount >= 0) { + outputStream.write(buffer, 0, readCount); + size += readCount; + } else { + break; + } + } + response.setContentLength(size); + } finally { + inputStream.close(); + } + + return true; + } else { + return false; + } + } + + @Contract("null, _ -> fail") + private static void setupContentType(String path, ServletResponse response) { + if (path != null) { + String mimeType = MimeUtil.getMimeType(path); + + if (mimeType != null) { + response.setContentType(mimeType); + return; + } + } + + throw new org.nocturne.exception.ServletException("Can't set content type for " + path + '.'); + } + + @Override + public void destroy() { + } + + private static final class MimeUtil { + private static final Map mimeTypeByExtension = new ConcurrentHashMap<>(); + + private static void add(String mimeType, String... extensions) { + for (String extension : extensions) { + if (mimeTypeByExtension.containsKey(extension)) { + throw new NocturneException("Already has registered mime type by " + extension + '.'); + } + mimeTypeByExtension.put(extension, mimeType); + } + } + + private static String getMimeType(String path) { + String extension = (path.indexOf('.') < 0 ? path : path.substring(path.lastIndexOf('.') + 1)).toLowerCase(); + String result = mimeTypeByExtension.get(extension); + if (result != null) { + return result; + } + + MimeType mimeType = eu.medsea.mimeutil.MimeUtil.getMostSpecificMimeType(eu.medsea.mimeutil.MimeUtil.getMimeTypes(new File(path).getName())); + if (mimeType != null) { + return mimeType.toString(); + } + + return "application/octet-stream"; + } + + static { + add("application/wasm", "wasm"); + add("application/json", "json"); + add("application/javascript", "js"); + add("application/pdf", "pdf"); + add("application/postscript", "ps"); + add("application/font-woff", "woff"); + add("application/xhtml+xml", "xhtml"); + add("application/xml-dtd", "dtd"); + add("application/zip", "zip"); + add("application/gzip", "gzip"); + add("application/x-tex", "tex"); + add("application/xml", "xml"); + add("audio/aac", "acc"); + add("audio/mpeg", "mp3"); + add("audio/ogg", "ogg"); + add("image/gif", "gif"); + add("image/jpeg", "jpeg"); + add("image/png", "png"); + add("image/svg+xml", "svg"); + add("image/tiff", "tiff"); + add("image/webp", "webp"); + add("image/bmp", "bmp"); + add("text/plain", "txt"); + add("text/css", "css"); + add("text/html", "html", "htm"); + add("text/x-java-source", "java"); + add("text/x-c", "cpp"); + add("text/x-c", "c"); + add("video/avi", "avi"); + add("video/mp4", "mp4"); + add("video/mpeg", "mpeg"); + } + } + +} diff --git a/code/src/main/java/org/nocturne/main/DispatchFilter.java b/code/src/main/java/org/nocturne/main/DispatchFilter.java index 93011e5..3a16ff4 100644 --- a/code/src/main/java/org/nocturne/main/DispatchFilter.java +++ b/code/src/main/java/org/nocturne/main/DispatchFilter.java @@ -1,223 +1,223 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.nocturne.exception.NocturneException; -import org.nocturne.exception.ReflectionException; -import org.nocturne.util.FileUtil; -import org.nocturne.util.ReflectionUtil; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - *

- * Main nocturne filter to dispatch requests. - *

- *

- * In will create new ReloadingClassLoader on each request - * (if changes found and more than 500 ms passed since last request) in the debug mode. - * This class loader will load updated classes of your application. - *

- *

- * In the production mode it uses usual webapp class loader. - *

- * - * @author Mike Mirzayanov - */ -@SuppressWarnings({"AccessOfSystemProperties", "UseOfPropertiesAsHashtable", "unchecked"}) -public class DispatchFilter implements Filter { - private static final ReloadingContext reloadingContext = ReloadingContext.getInstance(); - - static ClassLoader lastReloadingClassLoader; - private static Object debugModeRequestDispatcher; - private static long lastDebugModeAccess; - private static long lastDebugModeAccessReloadingClassPathHashCode; - - private static final RequestDispatcher productionModeRequestDispatcher = new RequestDispatcher(); - private static FilterConfig filterConfig; - - @Override - public void init(FilterConfig config) throws ServletException { - productionModeRequestDispatcher.init(config); - if (reloadingContext.isDebug()) { - initDebugMode(config); - } - - filterConfig = config; - } - - private static void initDebugMode(FilterConfig config) { - if (debugModeRequestDispatcher != null) { - try { - ReflectionUtil.invoke(debugModeRequestDispatcher, "init", config); - } catch (ReflectionException e) { - throw new NocturneException("Can't run debug mode request dispatcher init().", e); - } - } - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - if (servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) { - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - if (reloadingContext.getSkipRegex() != null && reloadingContext.getSkipRegex().matcher(request.getServletPath()).matches()) { - filterChain.doFilter(request, response); - } else { - servletRequest.setCharacterEncoding(StandardCharsets.UTF_8.name()); - servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name()); - if (reloadingContext.isDebug()) { - updateRequestDispatcher(); - - try { - ReflectionUtil.invoke(debugModeRequestDispatcher, "doFilter", request, response, filterChain); - } catch (ReflectionException e) { - throw new NocturneException("Can't run debug mode request dispatcher doFilter().", e); - } - } else { - productionModeRequestDispatcher.doFilter(request, response, filterChain); - } - } - } else { - filterChain.doFilter(servletRequest, servletResponse); - } - } - - @Override - public void destroy() { - if (reloadingContext.isDebug()) { - destroyDebugMode(); - } - reloadingContext.stop(); - - productionModeRequestDispatcher.destroy(); - } - - private static void destroyDebugMode() { - if (debugModeRequestDispatcher != null) { - try { - ReflectionUtil.invoke(debugModeRequestDispatcher, "destroy"); - } catch (ReflectionException e) { - throw new NocturneException("Can't run debug mode request dispatcher destroy().", e); - } - } - } - - static synchronized void updateRequestDispatcher() { - ClassLoader previousClassLoader = lastReloadingClassLoader; - updateReloadingClassLoader(); - if (previousClassLoader != lastReloadingClassLoader || debugModeRequestDispatcher == null) { - try { - destroyDebugMode(); - debugModeRequestDispatcher = lastReloadingClassLoader.loadClass( - RequestDispatcher.class.getName() - ).getConstructor().newInstance(); - ReflectionUtil.invoke(debugModeRequestDispatcher, "setReloadingClassLoader", lastReloadingClassLoader); - initDebugMode(filterConfig); - } catch (Exception e) { - throw new NocturneException("Can't load request dispatcher.", e); - } - } - } - - private static synchronized void updateReloadingClassLoader() { - if ("true".equals(System.getProperty("dreamcatcher.loaded"))) { - updateDreamcatcherReloadingClassLoader(); - } else { - updateNoDreamcatcherReloadingClassLoader(); - } - } - - private static void updateNoDreamcatcherReloadingClassLoader() { - if (lastReloadingClassLoader == null) { - lastReloadingClassLoader = new ReloadingClassLoader(); - lastDebugModeAccessReloadingClassPathHashCode = hashCode(reloadingContext.getReloadingClassPaths()); - lastDebugModeAccess = System.currentTimeMillis(); - } else { - if (System.currentTimeMillis() - lastDebugModeAccess > 1000) { - long hashCode = hashCode(reloadingContext.getReloadingClassPaths()); - - if (hashCode != lastDebugModeAccessReloadingClassPathHashCode) { - lastReloadingClassLoader = new ReloadingClassLoader(); - lastDebugModeAccessReloadingClassPathHashCode = hashCode; - } - - lastDebugModeAccess = System.currentTimeMillis(); - } - } - } - - private static void updateDreamcatcherReloadingClassLoader() { - if (lastReloadingClassLoader == null) { - lastReloadingClassLoader = new ReloadingClassLoader(); - } else { - if ("true".equals(System.getProperty("dreamcatcher.can-not-redefine-class"))) { - if (!System.getProperties().containsKey("nocturne.unused-reloading-class-loaders")) { - System.getProperties().put("nocturne.unused-reloading-class-loaders", new HashSet()); - } - - //noinspection unchecked - Set unusedReloadingClassLoaders - = (Set) System.getProperties().get("nocturne.unused-reloading-class-loaders"); - if (lastReloadingClassLoader instanceof ReloadingClassLoader) { - ReloadingClassLoader reloadingClassLoader = (ReloadingClassLoader) lastReloadingClassLoader; - unusedReloadingClassLoaders.add(reloadingClassLoader.getDelegationClassLoader()); - } - - ReloadingClassLoader reloadingClassLoader = new ReloadingClassLoader(); - lastReloadingClassLoader = reloadingClassLoader; - - System.out.println("NOCTURNE: ReloadingClassLoader created because of dreamcatcher.can-not-redefine-class=true" - + " [reloadingClassLoader=" + reloadingClassLoader - + ", delegationClassLoader=" + reloadingClassLoader.getDelegationClassLoader() - + ']'); - } - } - - System.setProperty("dreamcatcher.can-not-redefine-class", "false"); - } - - private static long hashCode(List paths) { - long result = 0; - long mul = 1; - for (File dir : paths) { - result += dir.hashCode() * mul; - mul *= 31; - result += hashCode(dir, 0) * mul; - mul *= 31; - } - return result; - } - - private static long hashCode(File file, long depth) { - long result = 0; - if (file.isFile()) { - if (useInHashCode(file)) { - result += file.getName().hashCode() * file.lastModified() * (depth + 1); - } - } else { - File[] files = file.listFiles(); - if (files != null) { - for (File nested : files) { - result += hashCode(nested, depth + 1); - } - } - } - return result; - } - - private static boolean useInHashCode(File file) { - String ext = FileUtil.getExt(file); - return ".class".equalsIgnoreCase(ext) || ".properties".equalsIgnoreCase(ext); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.nocturne.exception.NocturneException; +import org.nocturne.exception.ReflectionException; +import org.nocturne.util.FileUtil; +import org.nocturne.util.ReflectionUtil; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + *

+ * Main nocturne filter to dispatch requests. + *

+ *

+ * In will create new ReloadingClassLoader on each request + * (if changes found and more than 500 ms passed since last request) in the debug mode. + * This class loader will load updated classes of your application. + *

+ *

+ * In the production mode it uses usual webapp class loader. + *

+ * + * @author Mike Mirzayanov + */ +@SuppressWarnings({"AccessOfSystemProperties", "UseOfPropertiesAsHashtable", "unchecked"}) +public class DispatchFilter implements Filter { + private static final ReloadingContext reloadingContext = ReloadingContext.getInstance(); + + static ClassLoader lastReloadingClassLoader; + private static Object debugModeRequestDispatcher; + private static long lastDebugModeAccess; + private static long lastDebugModeAccessReloadingClassPathHashCode; + + private static final RequestDispatcher productionModeRequestDispatcher = new RequestDispatcher(); + private static FilterConfig filterConfig; + + @Override + public void init(FilterConfig config) throws ServletException { + productionModeRequestDispatcher.init(config); + if (reloadingContext.isDebug()) { + initDebugMode(config); + } + + filterConfig = config; + } + + private static void initDebugMode(FilterConfig config) { + if (debugModeRequestDispatcher != null) { + try { + ReflectionUtil.invoke(debugModeRequestDispatcher, "init", config); + } catch (ReflectionException e) { + throw new NocturneException("Can't run debug mode request dispatcher init().", e); + } + } + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + if (servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + if (reloadingContext.getSkipRegex() != null && reloadingContext.getSkipRegex().matcher(request.getServletPath()).matches()) { + filterChain.doFilter(request, response); + } else { + servletRequest.setCharacterEncoding(StandardCharsets.UTF_8.name()); + servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name()); + if (reloadingContext.isDebug()) { + updateRequestDispatcher(); + + try { + ReflectionUtil.invoke(debugModeRequestDispatcher, "doFilter", request, response, filterChain); + } catch (ReflectionException e) { + throw new NocturneException("Can't run debug mode request dispatcher doFilter().", e); + } + } else { + productionModeRequestDispatcher.doFilter(request, response, filterChain); + } + } + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + if (reloadingContext.isDebug()) { + destroyDebugMode(); + } + reloadingContext.stop(); + + productionModeRequestDispatcher.destroy(); + } + + private static void destroyDebugMode() { + if (debugModeRequestDispatcher != null) { + try { + ReflectionUtil.invoke(debugModeRequestDispatcher, "destroy"); + } catch (ReflectionException e) { + throw new NocturneException("Can't run debug mode request dispatcher destroy().", e); + } + } + } + + static synchronized void updateRequestDispatcher() { + ClassLoader previousClassLoader = lastReloadingClassLoader; + updateReloadingClassLoader(); + if (previousClassLoader != lastReloadingClassLoader || debugModeRequestDispatcher == null) { + try { + destroyDebugMode(); + debugModeRequestDispatcher = lastReloadingClassLoader.loadClass( + RequestDispatcher.class.getName() + ).getConstructor().newInstance(); + ReflectionUtil.invoke(debugModeRequestDispatcher, "setReloadingClassLoader", lastReloadingClassLoader); + initDebugMode(filterConfig); + } catch (Exception e) { + throw new NocturneException("Can't load request dispatcher.", e); + } + } + } + + private static synchronized void updateReloadingClassLoader() { + if ("true".equals(System.getProperty("dreamcatcher.loaded"))) { + updateDreamcatcherReloadingClassLoader(); + } else { + updateNoDreamcatcherReloadingClassLoader(); + } + } + + private static void updateNoDreamcatcherReloadingClassLoader() { + if (lastReloadingClassLoader == null) { + lastReloadingClassLoader = new ReloadingClassLoader(); + lastDebugModeAccessReloadingClassPathHashCode = hashCode(reloadingContext.getReloadingClassPaths()); + lastDebugModeAccess = System.currentTimeMillis(); + } else { + if (System.currentTimeMillis() - lastDebugModeAccess > 1000) { + long hashCode = hashCode(reloadingContext.getReloadingClassPaths()); + + if (hashCode != lastDebugModeAccessReloadingClassPathHashCode) { + lastReloadingClassLoader = new ReloadingClassLoader(); + lastDebugModeAccessReloadingClassPathHashCode = hashCode; + } + + lastDebugModeAccess = System.currentTimeMillis(); + } + } + } + + private static void updateDreamcatcherReloadingClassLoader() { + if (lastReloadingClassLoader == null) { + lastReloadingClassLoader = new ReloadingClassLoader(); + } else { + if ("true".equals(System.getProperty("dreamcatcher.can-not-redefine-class"))) { + if (!System.getProperties().containsKey("nocturne.unused-reloading-class-loaders")) { + System.getProperties().put("nocturne.unused-reloading-class-loaders", new HashSet()); + } + + //noinspection unchecked + Set unusedReloadingClassLoaders + = (Set) System.getProperties().get("nocturne.unused-reloading-class-loaders"); + if (lastReloadingClassLoader instanceof ReloadingClassLoader) { + ReloadingClassLoader reloadingClassLoader = (ReloadingClassLoader) lastReloadingClassLoader; + unusedReloadingClassLoaders.add(reloadingClassLoader.getDelegationClassLoader()); + } + + ReloadingClassLoader reloadingClassLoader = new ReloadingClassLoader(); + lastReloadingClassLoader = reloadingClassLoader; + + System.out.println("NOCTURNE: ReloadingClassLoader created because of dreamcatcher.can-not-redefine-class=true" + + " [reloadingClassLoader=" + reloadingClassLoader + + ", delegationClassLoader=" + reloadingClassLoader.getDelegationClassLoader() + + ']'); + } + } + + System.setProperty("dreamcatcher.can-not-redefine-class", "false"); + } + + private static long hashCode(List paths) { + long result = 0; + long mul = 1; + for (File dir : paths) { + result += dir.hashCode() * mul; + mul *= 31; + result += hashCode(dir, 0) * mul; + mul *= 31; + } + return result; + } + + private static long hashCode(File file, long depth) { + long result = 0; + if (file.isFile()) { + if (useInHashCode(file)) { + result += file.getName().hashCode() * file.lastModified() * (depth + 1); + } + } else { + File[] files = file.listFiles(); + if (files != null) { + for (File nested : files) { + result += hashCode(nested, depth + 1); + } + } + } + return result; + } + + private static boolean useInHashCode(File file) { + String ext = FileUtil.getExt(file); + return ".class".equalsIgnoreCase(ext) || ".properties".equalsIgnoreCase(ext); + } +} diff --git a/code/src/main/java/org/nocturne/main/Events.java b/code/src/main/java/org/nocturne/main/Events.java index 2896e6e..0d9606f 100644 --- a/code/src/main/java/org/nocturne/main/Events.java +++ b/code/src/main/java/org/nocturne/main/Events.java @@ -1,139 +1,139 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.nocturne.exception.IncorrectLogicException; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -/** - *

- * Use it to listen events and fire them. Any object can be event. Listeners - * are subscribed to class. When executed {@code fire(event)} all listeners for class - * event.getClass() will be notified. Also all listeners - * for event.getClass().getSuperclass() (and so on) will be notified. - *

- *

- * Use pair of methods beforeAction() and afterAction() to listen components. - * Any component will notify all listeners registered with beforeAction() - * before process action and will notify all listeners registered with afterAction() - * after process action. - *

- *

- * See also http://code.google.com/p/nocturne/wiki/RequestLifeCycle_RU - * or http://code.google.com/p/nocturne/wiki/RequestLifeCycle_EN - *

- * - * @author Mike Mirzayanov - */ -@SuppressWarnings("unused") -public class Events { - /** - * Each class has no more than MAX_LISTENER_COUNT listeners. - * If you are trying to add more, an exception will be thrown. - * Usually it means that you are trying to add listeners on each request, - * but you shouldn't do it - */ - private static final int MAX_LISTENER_COUNT = 20; - - private static final Scope COMMON_SCOPE = new Scope(); - private static final Scope BEFORE_ACTION_SCOPE = new Scope(); - private static final Scope AFTER_ACTION_SCOPE = new Scope(); - - /** - * Add listener to events of class "eventClass". - * - * @param Event class. - * @param eventClass Class to be listened. If event has "eventClass" as its - * superclass listeners will be notified too. - * @param listener Listener instance. - */ - public static void listen(Class eventClass, Listener listener) { - COMMON_SCOPE.listen(eventClass, listener); - } - - /** - * @param Event class. - * @param event Throwing event. All listeners registered for class event.getClass() - * or its superclass will be notified. - * @return Fired event. - */ - public static T fire(T event) { - return COMMON_SCOPE.fire(event); - } - - /** - * @param Component class. - * @param componentClass Component class to be listened. - * @param listener Listener which will be notified before any action - * for componentClass will be processed. - */ - public static void beforeAction(Class componentClass, Listener listener) { - BEFORE_ACTION_SCOPE.listen(componentClass, listener); - } - - static void fireBeforeAction(Component component) { - BEFORE_ACTION_SCOPE.fire(component); - } - - /** - * @param Component class. - * @param componentClass Component class to be listened. - * @param listener Listener which will be notified after any action - * for componentClass will be processed. - */ - public static void afterAction(Class componentClass, Listener listener) { - AFTER_ACTION_SCOPE.listen(componentClass, listener); - } - - static void fireAfterAction(Component component) { - AFTER_ACTION_SCOPE.fire(component); - } - - @SuppressWarnings("WeakerAccess") - private static class Scope { - /** - * Stores listeners for each . - */ - private final Map, Set>> listenersByEvent = new LinkedHashMap<>(); - - public void listen(Class eventClass, Listener listener) { - if (!listenersByEvent.containsKey(eventClass)) { - listenersByEvent.put(eventClass, new LinkedHashSet<>()); - } - - listenersByEvent.get(eventClass).add(listener); - - if (listenersByEvent.get(eventClass).size() > MAX_LISTENER_COUNT) { - throw new IncorrectLogicException(String.format( - "Too many listeners for %s event type. Are you sure your code is correct?", eventClass.getName() - )); - } - } - - @SuppressWarnings({"unchecked"}) - public T fire(T event) { - Class clazz = (Class) event.getClass(); - while (clazz != null) { - fireExactMatchedListeners(event, clazz); - clazz = clazz.getSuperclass(); - } - return event; - } - - @SuppressWarnings({"unchecked"}) - private void fireExactMatchedListeners(T event, Class clazz) { - if (listenersByEvent.containsKey(clazz)) { - Set> listeners = listenersByEvent.get(clazz); - for (Listener listener : listeners) { - Listener t = (Listener) listener; - t.onEvent(event); - } - } - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.nocturne.exception.IncorrectLogicException; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + *

+ * Use it to listen events and fire them. Any object can be event. Listeners + * are subscribed to class. When executed {@code fire(event)} all listeners for class + * event.getClass() will be notified. Also all listeners + * for event.getClass().getSuperclass() (and so on) will be notified. + *

+ *

+ * Use pair of methods beforeAction() and afterAction() to listen components. + * Any component will notify all listeners registered with beforeAction() + * before process action and will notify all listeners registered with afterAction() + * after process action. + *

+ *

+ * See also http://code.google.com/p/nocturne/wiki/RequestLifeCycle_RU + * or http://code.google.com/p/nocturne/wiki/RequestLifeCycle_EN + *

+ * + * @author Mike Mirzayanov + */ +@SuppressWarnings("unused") +public class Events { + /** + * Each class has no more than MAX_LISTENER_COUNT listeners. + * If you are trying to add more, an exception will be thrown. + * Usually it means that you are trying to add listeners on each request, + * but you shouldn't do it + */ + private static final int MAX_LISTENER_COUNT = 20; + + private static final Scope COMMON_SCOPE = new Scope(); + private static final Scope BEFORE_ACTION_SCOPE = new Scope(); + private static final Scope AFTER_ACTION_SCOPE = new Scope(); + + /** + * Add listener to events of class "eventClass". + * + * @param Event class. + * @param eventClass Class to be listened. If event has "eventClass" as its + * superclass listeners will be notified too. + * @param listener Listener instance. + */ + public static void listen(Class eventClass, Listener listener) { + COMMON_SCOPE.listen(eventClass, listener); + } + + /** + * @param Event class. + * @param event Throwing event. All listeners registered for class event.getClass() + * or its superclass will be notified. + * @return Fired event. + */ + public static T fire(T event) { + return COMMON_SCOPE.fire(event); + } + + /** + * @param Component class. + * @param componentClass Component class to be listened. + * @param listener Listener which will be notified before any action + * for componentClass will be processed. + */ + public static void beforeAction(Class componentClass, Listener listener) { + BEFORE_ACTION_SCOPE.listen(componentClass, listener); + } + + static void fireBeforeAction(Component component) { + BEFORE_ACTION_SCOPE.fire(component); + } + + /** + * @param Component class. + * @param componentClass Component class to be listened. + * @param listener Listener which will be notified after any action + * for componentClass will be processed. + */ + public static void afterAction(Class componentClass, Listener listener) { + AFTER_ACTION_SCOPE.listen(componentClass, listener); + } + + static void fireAfterAction(Component component) { + AFTER_ACTION_SCOPE.fire(component); + } + + @SuppressWarnings("WeakerAccess") + private static class Scope { + /** + * Stores listeners for each . + */ + private final Map, Set>> listenersByEvent = new LinkedHashMap<>(); + + public void listen(Class eventClass, Listener listener) { + if (!listenersByEvent.containsKey(eventClass)) { + listenersByEvent.put(eventClass, new LinkedHashSet<>()); + } + + listenersByEvent.get(eventClass).add(listener); + + if (listenersByEvent.get(eventClass).size() > MAX_LISTENER_COUNT) { + throw new IncorrectLogicException(String.format( + "Too many listeners for %s event type. Are you sure your code is correct?", eventClass.getName() + )); + } + } + + @SuppressWarnings({"unchecked"}) + public T fire(T event) { + Class clazz = (Class) event.getClass(); + while (clazz != null) { + fireExactMatchedListeners(event, clazz); + clazz = clazz.getSuperclass(); + } + return event; + } + + @SuppressWarnings({"unchecked"}) + private void fireExactMatchedListeners(T event, Class clazz) { + if (listenersByEvent.containsKey(clazz)) { + Set> listeners = listenersByEvent.get(clazz); + for (Listener listener : listeners) { + Listener t = (Listener) listener; + t.onEvent(event); + } + } + } + } +} diff --git a/code/src/main/java/org/nocturne/main/Frame.java b/code/src/main/java/org/nocturne/main/Frame.java index 882c66d..d4e14c3 100644 --- a/code/src/main/java/org/nocturne/main/Frame.java +++ b/code/src/main/java/org/nocturne/main/Frame.java @@ -1,159 +1,159 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import freemarker.template.TemplateException; -import io.prometheus.client.Summary; -import org.jetbrains.annotations.Nullable; -import org.nocturne.cache.CacheHandler; -import org.nocturne.exception.FreemarkerException; -import org.nocturne.exception.InterruptException; -import org.nocturne.prometheus.Prometheus; -import org.nocturne.util.ReflectionUtil; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; - -/** - * Often there are small pieces of logic+view exist. For example, panel - * with recent news can be on many pages. You can code it as Frame. Frame is - * a special component which injects into some page or other component and has - * own template. - * - * @author Mike Mirzayanov - */ -@SuppressWarnings("WeakerAccess") -public abstract class Frame extends Component { - /** - * @return Current processing page for current request. - */ - @SuppressWarnings("unused") - public Page getCurrentPage() { - return ApplicationContext.getInstance().getCurrentPage(); - } - - public String parseTemplate() { - String simpleClassName = ReflectionUtil.getOriginalClass(getClass()).getSimpleName(); - - Prometheus.getFramesCounter().labels(simpleClassName).inc(); - Summary.Timer overallTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "overall").startTimer(); - - try { - return internalParseTemplate(simpleClassName); - } finally { - overallTimer.observeDuration(); - } - } - - @Nullable - private String internalParseTemplate(String simpleClassName) { - prepareForAction(); - - CacheHandler cacheHandler = getCacheHandler(); - String result = null; - if (cacheHandler != null && !isSkipTemplate()) { - result = cacheHandler.intercept(this); - } - - try { - if (result == null) { - boolean interrupted = false; - - Summary.Timer initializeActionTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "initializeAction").startTimer(); - try { - initializeAction(); - } catch (InterruptException ignored) { - interrupted = true; - } finally { - initializeActionTimer.observeDuration(); - } - - if (!interrupted) { - // Before action. - { - Summary.Timer beforeActionTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "beforeAction").startTimer(); - try { - Events.fireBeforeAction(this); - } finally { - beforeActionTimer.observeDuration(); - } - } - - // Action. - { - Summary.Timer actionTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "action").startTimer(); - try { - internalRunAction(getActionName()); - } catch (InterruptException ignored) { - // No operations. - } finally { - actionTimer.observeDuration(); - } - } - - // After action. - { - Summary.Timer afterActionTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "afterAction").startTimer(); - try { - Events.fireAfterAction(this); - } finally { - afterActionTimer.observeDuration(); - } - } - } - - Summary.Timer finalizeActionTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "finalizeAction").startTimer(); - try { - finalizeAction(); - } catch (InterruptException ignored) { - // No operations. - } finally { - finalizeActionTimer.observeDuration(); - } - - if (isSkipTemplate()) { - return null; - } else { - StringWriter writer = new StringWriter(4096); - Map params = new HashMap<>(internalGetTemplateMap()); - params.putAll(ApplicationContext.getInstance().getCurrentPage().internalGetGlobalTemplateMap()); - - Summary.Timer templateTimer = Prometheus.getFramesLatencySeconds() - .labels(simpleClassName, "template").startTimer(); - try { - getTemplate().process(params, writer); - writer.close(); - - result = writer.getBuffer().toString(); - if (cacheHandler != null) { - cacheHandler.postprocess(this, result); - } - return result; - } finally { - templateTimer.observeDuration(); - } - } - } else { - return result; - } - } catch (TemplateException | IOException e) { - throw new FreemarkerException("Can't parse frame " + getClass().getSimpleName() + '.', e); - } finally { - finalizeAfterAction(); - } - } - - @Override - protected void setupRequestParams() { - setupRequestParamsForFrame(); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import freemarker.template.TemplateException; +import io.prometheus.client.Summary; +import org.jetbrains.annotations.Nullable; +import org.nocturne.cache.CacheHandler; +import org.nocturne.exception.FreemarkerException; +import org.nocturne.exception.InterruptException; +import org.nocturne.prometheus.Prometheus; +import org.nocturne.util.ReflectionUtil; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Often there are small pieces of logic+view exist. For example, panel + * with recent news can be on many pages. You can code it as Frame. Frame is + * a special component which injects into some page or other component and has + * own template. + * + * @author Mike Mirzayanov + */ +@SuppressWarnings("WeakerAccess") +public abstract class Frame extends Component { + /** + * @return Current processing page for current request. + */ + @SuppressWarnings("unused") + public Page getCurrentPage() { + return ApplicationContext.getInstance().getCurrentPage(); + } + + public String parseTemplate() { + String simpleClassName = ReflectionUtil.getOriginalClass(getClass()).getSimpleName(); + + Prometheus.getFramesCounter().labels(simpleClassName).inc(); + Summary.Timer overallTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "overall").startTimer(); + + try { + return internalParseTemplate(simpleClassName); + } finally { + overallTimer.observeDuration(); + } + } + + @Nullable + private String internalParseTemplate(String simpleClassName) { + prepareForAction(); + + CacheHandler cacheHandler = getCacheHandler(); + String result = null; + if (cacheHandler != null && !isSkipTemplate()) { + result = cacheHandler.intercept(this); + } + + try { + if (result == null) { + boolean interrupted = false; + + Summary.Timer initializeActionTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "initializeAction").startTimer(); + try { + initializeAction(); + } catch (InterruptException ignored) { + interrupted = true; + } finally { + initializeActionTimer.observeDuration(); + } + + if (!interrupted) { + // Before action. + { + Summary.Timer beforeActionTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "beforeAction").startTimer(); + try { + Events.fireBeforeAction(this); + } finally { + beforeActionTimer.observeDuration(); + } + } + + // Action. + { + Summary.Timer actionTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "action").startTimer(); + try { + internalRunAction(getActionName()); + } catch (InterruptException ignored) { + // No operations. + } finally { + actionTimer.observeDuration(); + } + } + + // After action. + { + Summary.Timer afterActionTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "afterAction").startTimer(); + try { + Events.fireAfterAction(this); + } finally { + afterActionTimer.observeDuration(); + } + } + } + + Summary.Timer finalizeActionTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "finalizeAction").startTimer(); + try { + finalizeAction(); + } catch (InterruptException ignored) { + // No operations. + } finally { + finalizeActionTimer.observeDuration(); + } + + if (isSkipTemplate()) { + return null; + } else { + StringWriter writer = new StringWriter(4096); + Map params = new HashMap<>(internalGetTemplateMap()); + params.putAll(ApplicationContext.getInstance().getCurrentPage().internalGetGlobalTemplateMap()); + + Summary.Timer templateTimer = Prometheus.getFramesLatencySeconds() + .labels(simpleClassName, "template").startTimer(); + try { + getTemplate().process(params, writer); + writer.close(); + + result = writer.getBuffer().toString(); + if (cacheHandler != null) { + cacheHandler.postprocess(this, result); + } + return result; + } finally { + templateTimer.observeDuration(); + } + } + } else { + return result; + } + } catch (TemplateException | IOException e) { + throw new FreemarkerException("Can't parse frame " + getClass().getSimpleName() + '.', e); + } finally { + finalizeAfterAction(); + } + } + + @Override + protected void setupRequestParams() { + setupRequestParamsForFrame(); + } +} diff --git a/code/src/main/java/org/nocturne/main/FrameDirective.java b/code/src/main/java/org/nocturne/main/FrameDirective.java index 62895dd..f1222ec 100644 --- a/code/src/main/java/org/nocturne/main/FrameDirective.java +++ b/code/src/main/java/org/nocturne/main/FrameDirective.java @@ -1,96 +1,96 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import freemarker.core.Environment; -import freemarker.template.*; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static org.nocturne.template.impl.ComponentTemplatePreprocessor.UNIQUE_MAGIC_OPEN_PREFIX; -import static org.nocturne.template.impl.ComponentTemplatePreprocessor.UNIQUE_MAGIC_CLOSE_PREFIX; - -/** - * Use code like {@code <@frame name="loginFormFrame"/>} to inject - * frame content into some template. But frame should be parsed into - * variable loginFormFrame on action processing phase. - * - * @author Mike Mirzayanov - */ -public class FrameDirective implements TemplateDirectiveModel { - private final Set UNIQUE_RENDER_KEYS = new HashSet<>(); - - FrameDirective() { - // No operations. - } - - @Override - public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException { - if (map.size() != 1) { - throw new TemplateException("Frame directive expects the only 'name' argument.", environment); - } - - if (!map.containsKey("name")) { - throw new TemplateException("Frame directive expects the only 'name' argument.", environment); - } - - Object name = map.get("name"); - - if (name instanceof SimpleScalar) { - Component component = ApplicationContext.getInstance().getCurrentComponent(); - - String frameName = ((TemplateScalarModel) name).getAsString(); - String html = component.getFrameHtml(frameName); - if (html == null) { - throw new TemplateException("Frame directive expected parsed frame '" + frameName + "', but didn't find.", environment); - } else { - if (html.contains(UNIQUE_MAGIC_OPEN_PREFIX)) { - html = processComponentUniques(new StringBuilder(html)); - } - } - - environment.getOut().write(html); - environment.getOut().flush(); - } else { - throw new TemplateException("Frame directive parameter 'name' should be a String.", environment); - } - } - - @Nonnull - String processComponentUniques(@Nonnull StringBuilder sb) { - int open; - while ((open = sb.indexOf(UNIQUE_MAGIC_OPEN_PREFIX)) >= 0) { - int close = sb.indexOf(UNIQUE_MAGIC_CLOSE_PREFIX, open + 1); - if (open < close) { - int i = open + UNIQUE_MAGIC_OPEN_PREFIX.length(); - while (i < sb.length() && sb.charAt(i) != '>') { - i++; - } - int j = close + UNIQUE_MAGIC_CLOSE_PREFIX.length(); - while (j < sb.length() && sb.charAt(j) != '>') { - j++; - } - if (i < sb.length() && sb.charAt(i) == '>' - && j < sb.length() && sb.charAt(j) == '>') { - String openKey = sb.substring(open + UNIQUE_MAGIC_OPEN_PREFIX.length(), i); - String closeKey = sb.substring(close + UNIQUE_MAGIC_CLOSE_PREFIX.length(), j); - if (openKey.equals(closeKey) && !openKey.isEmpty()) { - if (UNIQUE_RENDER_KEYS.contains(openKey)) { - sb.delete(open, j + 2); - } else { - UNIQUE_RENDER_KEYS.add(openKey); - sb.delete(close, j + 2); - sb.delete(open, i + 2); - } - } - } - } - } - return sb.toString(); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import freemarker.core.Environment; +import freemarker.template.*; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.nocturne.template.impl.ComponentTemplatePreprocessor.UNIQUE_MAGIC_OPEN_PREFIX; +import static org.nocturne.template.impl.ComponentTemplatePreprocessor.UNIQUE_MAGIC_CLOSE_PREFIX; + +/** + * Use code like {@code <@frame name="loginFormFrame"/>} to inject + * frame content into some template. But frame should be parsed into + * variable loginFormFrame on action processing phase. + * + * @author Mike Mirzayanov + */ +public class FrameDirective implements TemplateDirectiveModel { + private final Set UNIQUE_RENDER_KEYS = new HashSet<>(); + + FrameDirective() { + // No operations. + } + + @Override + public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException { + if (map.size() != 1) { + throw new TemplateException("Frame directive expects the only 'name' argument.", environment); + } + + if (!map.containsKey("name")) { + throw new TemplateException("Frame directive expects the only 'name' argument.", environment); + } + + Object name = map.get("name"); + + if (name instanceof SimpleScalar) { + Component component = ApplicationContext.getInstance().getCurrentComponent(); + + String frameName = ((TemplateScalarModel) name).getAsString(); + String html = component.getFrameHtml(frameName); + if (html == null) { + throw new TemplateException("Frame directive expected parsed frame '" + frameName + "', but didn't find.", environment); + } else { + if (html.contains(UNIQUE_MAGIC_OPEN_PREFIX)) { + html = processComponentUniques(new StringBuilder(html)); + } + } + + environment.getOut().write(html); + environment.getOut().flush(); + } else { + throw new TemplateException("Frame directive parameter 'name' should be a String.", environment); + } + } + + @Nonnull + String processComponentUniques(@Nonnull StringBuilder sb) { + int open; + while ((open = sb.indexOf(UNIQUE_MAGIC_OPEN_PREFIX)) >= 0) { + int close = sb.indexOf(UNIQUE_MAGIC_CLOSE_PREFIX, open + 1); + if (open < close) { + int i = open + UNIQUE_MAGIC_OPEN_PREFIX.length(); + while (i < sb.length() && sb.charAt(i) != '>') { + i++; + } + int j = close + UNIQUE_MAGIC_CLOSE_PREFIX.length(); + while (j < sb.length() && sb.charAt(j) != '>') { + j++; + } + if (i < sb.length() && sb.charAt(i) == '>' + && j < sb.length() && sb.charAt(j) == '>') { + String openKey = sb.substring(open + UNIQUE_MAGIC_OPEN_PREFIX.length(), i); + String closeKey = sb.substring(close + UNIQUE_MAGIC_CLOSE_PREFIX.length(), j); + if (openKey.equals(closeKey) && !openKey.isEmpty()) { + if (UNIQUE_RENDER_KEYS.contains(openKey)) { + sb.delete(open, j + 2); + } else { + UNIQUE_RENDER_KEYS.add(openKey); + sb.delete(close, j + 2); + sb.delete(open, i + 2); + } + } + } + } + } + return sb.toString(); + } +} diff --git a/code/src/main/java/org/nocturne/main/GenericIocModule.java b/code/src/main/java/org/nocturne/main/GenericIocModule.java index 708a92f..eb3b170 100644 --- a/code/src/main/java/org/nocturne/main/GenericIocModule.java +++ b/code/src/main/java/org/nocturne/main/GenericIocModule.java @@ -1,69 +1,69 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import com.google.inject.Binder; -import com.google.inject.Module; -import com.google.inject.TypeLiteral; -import com.google.inject.matcher.AbstractMatcher; -import com.google.inject.matcher.Matcher; -import com.google.inject.matcher.Matchers; -import com.google.inject.spi.InjectionListener; -import com.google.inject.spi.TypeEncounter; -import com.google.inject.spi.TypeListener; -import org.nocturne.module.Configuration; - -import java.util.List; - -/** - * Wraps IoC module specified by nocturne.guice-module-class-name and invokes - * IoC modules for all registered application modules. - * - * @author Mike Mirzayanov - */ -class GenericIocModule implements Module { - private Module module; - - public void setModule(Module module) { - this.module = module; - } - - @Override - public void configure(Binder binder) { - binder.bindListener(new ClassToTypeLiteralMatcherAdapter(Matchers.subclassesOf(Component.class)), new TypeListener() { - @Override - public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { - typeEncounter.register((InjectionListener) o -> { - Component component = (Component) o; - component.resetFields(); - }); - } - }); - - if (module != null) { - module.configure(binder); - } - - List modules = ApplicationContext.getInstance().getModules(); - - for (org.nocturne.module.Module item : modules) { - Configuration configuration = item.getConfiguration(); - configuration.bind(binder); - } - - } - - private static final class ClassToTypeLiteralMatcherAdapter extends AbstractMatcher { - private final Matcher classMatcher; - - private ClassToTypeLiteralMatcherAdapter(Matcher classMatcher) { - this.classMatcher = classMatcher; - } - - @Override - public boolean matches(TypeLiteral typeLiteral) { - return classMatcher.matches(typeLiteral.getRawType()); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; +import com.google.inject.matcher.Matchers; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; +import org.nocturne.module.Configuration; + +import java.util.List; + +/** + * Wraps IoC module specified by nocturne.guice-module-class-name and invokes + * IoC modules for all registered application modules. + * + * @author Mike Mirzayanov + */ +class GenericIocModule implements Module { + private Module module; + + public void setModule(Module module) { + this.module = module; + } + + @Override + public void configure(Binder binder) { + binder.bindListener(new ClassToTypeLiteralMatcherAdapter(Matchers.subclassesOf(Component.class)), new TypeListener() { + @Override + public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { + typeEncounter.register((InjectionListener) o -> { + Component component = (Component) o; + component.resetFields(); + }); + } + }); + + if (module != null) { + module.configure(binder); + } + + List modules = ApplicationContext.getInstance().getModules(); + + for (org.nocturne.module.Module item : modules) { + Configuration configuration = item.getConfiguration(); + configuration.bind(binder); + } + + } + + private static final class ClassToTypeLiteralMatcherAdapter extends AbstractMatcher { + private final Matcher classMatcher; + + private ClassToTypeLiteralMatcherAdapter(Matcher classMatcher) { + this.classMatcher = classMatcher; + } + + @Override + public boolean matches(TypeLiteral typeLiteral) { + return classMatcher.matches(typeLiteral.getRawType()); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/HttpMethod.java b/code/src/main/java/org/nocturne/main/HttpMethod.java index 8eb452d..cef4842 100644 --- a/code/src/main/java/org/nocturne/main/HttpMethod.java +++ b/code/src/main/java/org/nocturne/main/HttpMethod.java @@ -1,15 +1,15 @@ -package org.nocturne.main; - -public enum HttpMethod { - GET, - POST, - PUT, - HEAD, - OPTIONS, - PATCH, - DELETE, - TRACE, - LINK, - UNLINK, - CONNECT -} +package org.nocturne.main; + +public enum HttpMethod { + GET, + POST, + PUT, + HEAD, + OPTIONS, + PATCH, + DELETE, + TRACE, + LINK, + UNLINK, + CONNECT +} diff --git a/code/src/main/java/org/nocturne/main/LinkedRequestRouter.java b/code/src/main/java/org/nocturne/main/LinkedRequestRouter.java index 779c96d..d5658fe 100644 --- a/code/src/main/java/org/nocturne/main/LinkedRequestRouter.java +++ b/code/src/main/java/org/nocturne/main/LinkedRequestRouter.java @@ -1,60 +1,60 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.nocturne.link.LinkMatchResult; -import org.nocturne.link.Links; -import org.nocturne.util.RequestUtil; -import org.nocturne.util.StringUtil; - -import java.util.List; -import java.util.Map; - -/** - * @author Mike Mirzayanov - */ -@SuppressWarnings("unused") -public class LinkedRequestRouter implements RequestRouter { - @Override - public Resolution route(String path, Map> parameterMap) { - String action = RequestUtil.getFirst(parameterMap, "action"); - LinkMatchResult linkMatchResult = Links.match(path); - - if (linkMatchResult != null) { - ApplicationContext.getInstance().setLink(linkMatchResult.getLink()); - - Map attrs = linkMatchResult.getAttributes(); - if (attrs != null) { - for (Map.Entry entry : attrs.entrySet()) { - ApplicationContext.getInstance().getRequest().setAttribute( - ApplicationContext.getAdditionalParamsRequestAttributePrefix() + entry.getKey(), entry.getValue() - ); - if ("action".equals(entry.getKey())) { - action = entry.getValue(); - } - } - } - - if (StringUtil.isEmpty(action)) { - action = linkMatchResult.getLink().action(); - } - - //noinspection ConstantConditions - if (action == null) { - action = ""; - } - - Resolution resolution = new Resolution(linkMatchResult.getPageClass().getName(), action); - if (attrs != null) { - for (Map.Entry entry : attrs.entrySet()) { - resolution.addOverrideParameter(entry.getKey(), entry.getValue()); - } - } - - return resolution; - } else { - return null; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.nocturne.link.LinkMatchResult; +import org.nocturne.link.Links; +import org.nocturne.util.RequestUtil; +import org.nocturne.util.StringUtil; + +import java.util.List; +import java.util.Map; + +/** + * @author Mike Mirzayanov + */ +@SuppressWarnings("unused") +public class LinkedRequestRouter implements RequestRouter { + @Override + public Resolution route(String path, Map> parameterMap) { + String action = RequestUtil.getFirst(parameterMap, "action"); + LinkMatchResult linkMatchResult = Links.match(path); + + if (linkMatchResult != null) { + ApplicationContext.getInstance().setLink(linkMatchResult.getLink()); + + Map attrs = linkMatchResult.getAttributes(); + if (attrs != null) { + for (Map.Entry entry : attrs.entrySet()) { + ApplicationContext.getInstance().getRequest().setAttribute( + ApplicationContext.getAdditionalParamsRequestAttributePrefix() + entry.getKey(), entry.getValue() + ); + if ("action".equals(entry.getKey())) { + action = entry.getValue(); + } + } + } + + if (StringUtil.isEmpty(action)) { + action = linkMatchResult.getLink().action(); + } + + //noinspection ConstantConditions + if (action == null) { + action = ""; + } + + Resolution resolution = new Resolution(linkMatchResult.getPageClass().getName(), action); + if (attrs != null) { + for (Map.Entry entry : attrs.entrySet()) { + resolution.addOverrideParameter(entry.getKey(), entry.getValue()); + } + } + + return resolution; + } else { + return null; + } + } +} diff --git a/code/src/main/java/org/nocturne/main/Listener.java b/code/src/main/java/org/nocturne/main/Listener.java index 29396b2..51fd4ef 100644 --- a/code/src/main/java/org/nocturne/main/Listener.java +++ b/code/src/main/java/org/nocturne/main/Listener.java @@ -1,20 +1,20 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -/** - * Event listener. - * - * @author Mike Mirzayanov - */ -public interface Listener { - /** - * This method will be invoked in case of invocation - * Events.fire(event) and if the listener was - * subscribed to event.getClass() or its superclass. - * - * @param event Event instance. - */ - void onEvent(T event); -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +/** + * Event listener. + * + * @author Mike Mirzayanov + */ +public interface Listener { + /** + * This method will be invoked in case of invocation + * Events.fire(event) and if the listener was + * subscribed to event.getClass() or its superclass. + * + * @param event Event instance. + */ + void onEvent(T event); +} diff --git a/code/src/main/java/org/nocturne/main/Page.java b/code/src/main/java/org/nocturne/main/Page.java index ea147a8..54d1e23 100644 --- a/code/src/main/java/org/nocturne/main/Page.java +++ b/code/src/main/java/org/nocturne/main/Page.java @@ -1,305 +1,305 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import freemarker.template.TemplateException; -import io.prometheus.client.Summary; -import org.nocturne.cache.CacheHandler; -import org.nocturne.exception.*; -import org.nocturne.postprocess.ResponsePostprocessor; -import org.nocturne.prometheus.Prometheus; -import org.nocturne.util.ReflectionUtil; - -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.*; - -/** - * Main controller class. Read http://code.google.com/p/nocturne/wiki/RequestLifeCycle_RU - * or http://code.google.com/p/nocturne/wiki/RequestLifeCycle_EN - * for details of its lifecycle. - * - * @author Mike Mirzayanov - */ -@SuppressWarnings({"unused", "WeakerAccess"}) -public abstract class Page extends Component { - /** - * Global template variables map. - */ - private Map globalTemplateMap; - - /** - * Stores additional css resources added by addCss() from the page or internal frames. - */ - private final Set cssSet = new LinkedHashSet<>(); - - /** - * Stores additional js resources added by addJs() from the page or internal frames. - */ - private final Set jsSet = new LinkedHashSet<>(); - - /** - * Default is null, which means no postprocessing. - */ - private ResponsePostprocessor responsePostprocessor; - - /** - * Request-scoped cache. For internal usage. - */ - private Map requestCache; - - /** - * Flag, which stores should workflow be passed to filterChain. - */ - private boolean processChain; - - Map getRequestCache() { - return requestCache; - } - - void setRequestCache(Map requestCache) { - this.requestCache = requestCache; - } - - /** - * @return {@code true} iff setProcessChain(true) has been called and - * it means that workflow will be passed to filterChain after page processed. - */ - public boolean isProcessChain() { - return processChain; - } - - /** - * @return Request postprocessor or {@code null} if not set. - */ - public ResponsePostprocessor getResponsePostprocessor() { - return responsePostprocessor; - } - - /** - * @param responsePostprocessor Request postprocessor to postprocess response from the current page. - */ - public void setResponsePostprocessor(ResponsePostprocessor responsePostprocessor) { - this.responsePostprocessor = responsePostprocessor; - } - - /** - * @param processChain {@code true} if you want to use filterChain after - * page usage. - */ - protected void setProcessChain(boolean processChain) { - this.processChain = processChain; - } - - void putRequestCache(String key, Object value) { - if (requestCache != null) { - requestCache.put(key, value); - } - } - - Object getRequestCache(String key) { - return requestCache == null ? null : requestCache.get(key); - } - - void removeRequestCache(String key) { - if (requestCache != null) { - requestCache.remove(key); - } - } - - Set getCssSet() { - return cssSet; - } - - Set getJsSet() { - return jsSet; - } - - public Map getGlobalTemplateMap() { - return globalTemplateMap == null ? null : new HashMap<>(globalTemplateMap); - } - - Map internalGetGlobalTemplateMap() { - return globalTemplateMap; - } - - /** - * Handles main part of page workflow and parses template (writes it to response) if needed. - */ - public void parseTemplate() { - String simpleClassName = ReflectionUtil.getOriginalClass(getClass()).getSimpleName(); - - Prometheus.getPagesCounter().labels(simpleClassName).inc(); - Summary.Timer overallTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "overall").startTimer(); - - try { - prepareForAction(); - - CacheHandler cacheHandler = getCacheHandler(); - String result = null; - if (cacheHandler != null && !isSkipTemplate()) { - result = cacheHandler.intercept(this); - result = handleRequestPostprocessor(result); - } - - if (result == null) { - boolean interrupted = false; - - Summary.Timer initializeActionTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "initializeAction").startTimer(); - try { - initializeAction(); - } catch (InterruptException e) { - interrupted = true; - } finally { - initializeActionTimer.observeDuration(); - } - - if (!interrupted) { - // Before action. - { - Summary.Timer beforeActionTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "beforeAction").startTimer(); - try { - Events.fireBeforeAction(this); - } finally { - beforeActionTimer.observeDuration(); - } - } - - // Action. - { - Summary.Timer actionTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "action").startTimer(); - try { - internalRunAction(getActionName()); - } catch (InterruptException ignored) { - // No operations. - } finally { - actionTimer.observeDuration(); - } - } - - // After action. - { - Summary.Timer afterActionTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "afterAction").startTimer(); - try { - Events.fireAfterAction(this); - } finally { - afterActionTimer.observeDuration(); - } - } - } - - Summary.Timer finalizeActionTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "finalizeAction").startTimer(); - try { - finalizeAction(); - } catch (InterruptException ignored) { - // No operations. - } finally { - finalizeActionTimer.observeDuration(); - } - - if (!isSkipTemplate()) { - Map params = new HashMap<>(internalGetTemplateMap()); - params.putAll(internalGetGlobalTemplateMap()); - - Summary.Timer templateTimer = Prometheus.getPagesLatencySeconds() - .labels(simpleClassName, "template").startTimer(); - try { - getTemplate().setOutputEncoding(StandardCharsets.UTF_8.name()); - - StringWriter stringWriter = new StringWriter(65536); - getTemplate().process(params, stringWriter); - stringWriter.close(); - - result = ((FrameDirective) getGlobalTemplateMap().get("frame")).processComponentUniques( - new StringBuilder(stringWriter.getBuffer())); - - if (cacheHandler != null) { - cacheHandler.postprocess(this, result); - } - - result = handleRequestPostprocessor(result); - } catch (TemplateException e) { - throw new FreemarkerException("Can't parse template for page " + getClass().getName() + '.', e); - } catch (IOException e) { - if (!e.toString().contains("ClientAbortException")) { - throw new FreemarkerException("Can't parse template for page " + getClass().getName() + '.', e); - } - } finally { - templateTimer.observeDuration(); - } - } - } - - if (result != null) { - getOutputStream().write(result.getBytes(StandardCharsets.UTF_8)); - } - } catch (AbortException ignored) { - // No operations. - } catch (IOException e) { - throw new FreemarkerException("Can't write page " + getClass().getName() + '.', e); - } finally { - finalizeAfterAction(); - overallTimer.observeDuration(); - } - } - - void finalizeAfterAction() { - requestCache = null; - globalTemplateMap = null; - super.finalizeAfterAction(); - } - - private String handleRequestPostprocessor(String result) { - ResponsePostprocessor postprocessor = responsePostprocessor; - if (postprocessor != null) { - result = postprocessor.postprocess(this, result); - } - return result; - } - - @Override - void prepareForAction() { - jsSet.clear(); - cssSet.clear(); - - setupCurrentPage(); - - globalTemplateMap = Collections.synchronizedMap(new HashMap<>()); - requestCache = Collections.synchronizedMap(new HashMap<>()); - - super.prepareForAction(); - - internalGetGlobalTemplateMap().put("frame", new FrameDirective()); - internalGetGlobalTemplateMap().put("once", new OnceDirective()); - - internalGetTemplateMap().put("css", cssSet); - internalGetTemplateMap().put("js", jsSet); - - processChain = false; - } - - @Override - protected final void setupRequestParams() { - setupRequestParamsForPage(); - } - - private void setupCurrentPage() { - if (ApplicationContext.getInstance().isDebug()) { - try { - ReflectionUtil.invoke(ApplicationContext.getInstance(), "setCurrentPage", this); - } catch (ReflectionException e) { - throw new NocturneException("Can't set current page.", e); - } - } else { - ApplicationContext.getInstance().setCurrentPage(this); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import freemarker.template.TemplateException; +import io.prometheus.client.Summary; +import org.nocturne.cache.CacheHandler; +import org.nocturne.exception.*; +import org.nocturne.postprocess.ResponsePostprocessor; +import org.nocturne.prometheus.Prometheus; +import org.nocturne.util.ReflectionUtil; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * Main controller class. Read http://code.google.com/p/nocturne/wiki/RequestLifeCycle_RU + * or http://code.google.com/p/nocturne/wiki/RequestLifeCycle_EN + * for details of its lifecycle. + * + * @author Mike Mirzayanov + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public abstract class Page extends Component { + /** + * Global template variables map. + */ + private Map globalTemplateMap; + + /** + * Stores additional css resources added by addCss() from the page or internal frames. + */ + private final Set cssSet = new LinkedHashSet<>(); + + /** + * Stores additional js resources added by addJs() from the page or internal frames. + */ + private final Set jsSet = new LinkedHashSet<>(); + + /** + * Default is null, which means no postprocessing. + */ + private ResponsePostprocessor responsePostprocessor; + + /** + * Request-scoped cache. For internal usage. + */ + private Map requestCache; + + /** + * Flag, which stores should workflow be passed to filterChain. + */ + private boolean processChain; + + Map getRequestCache() { + return requestCache; + } + + void setRequestCache(Map requestCache) { + this.requestCache = requestCache; + } + + /** + * @return {@code true} iff setProcessChain(true) has been called and + * it means that workflow will be passed to filterChain after page processed. + */ + public boolean isProcessChain() { + return processChain; + } + + /** + * @return Request postprocessor or {@code null} if not set. + */ + public ResponsePostprocessor getResponsePostprocessor() { + return responsePostprocessor; + } + + /** + * @param responsePostprocessor Request postprocessor to postprocess response from the current page. + */ + public void setResponsePostprocessor(ResponsePostprocessor responsePostprocessor) { + this.responsePostprocessor = responsePostprocessor; + } + + /** + * @param processChain {@code true} if you want to use filterChain after + * page usage. + */ + protected void setProcessChain(boolean processChain) { + this.processChain = processChain; + } + + void putRequestCache(String key, Object value) { + if (requestCache != null) { + requestCache.put(key, value); + } + } + + Object getRequestCache(String key) { + return requestCache == null ? null : requestCache.get(key); + } + + void removeRequestCache(String key) { + if (requestCache != null) { + requestCache.remove(key); + } + } + + Set getCssSet() { + return cssSet; + } + + Set getJsSet() { + return jsSet; + } + + public Map getGlobalTemplateMap() { + return globalTemplateMap == null ? null : new HashMap<>(globalTemplateMap); + } + + Map internalGetGlobalTemplateMap() { + return globalTemplateMap; + } + + /** + * Handles main part of page workflow and parses template (writes it to response) if needed. + */ + public void parseTemplate() { + String simpleClassName = ReflectionUtil.getOriginalClass(getClass()).getSimpleName(); + + Prometheus.getPagesCounter().labels(simpleClassName).inc(); + Summary.Timer overallTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "overall").startTimer(); + + try { + prepareForAction(); + + CacheHandler cacheHandler = getCacheHandler(); + String result = null; + if (cacheHandler != null && !isSkipTemplate()) { + result = cacheHandler.intercept(this); + result = handleRequestPostprocessor(result); + } + + if (result == null) { + boolean interrupted = false; + + Summary.Timer initializeActionTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "initializeAction").startTimer(); + try { + initializeAction(); + } catch (InterruptException e) { + interrupted = true; + } finally { + initializeActionTimer.observeDuration(); + } + + if (!interrupted) { + // Before action. + { + Summary.Timer beforeActionTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "beforeAction").startTimer(); + try { + Events.fireBeforeAction(this); + } finally { + beforeActionTimer.observeDuration(); + } + } + + // Action. + { + Summary.Timer actionTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "action").startTimer(); + try { + internalRunAction(getActionName()); + } catch (InterruptException ignored) { + // No operations. + } finally { + actionTimer.observeDuration(); + } + } + + // After action. + { + Summary.Timer afterActionTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "afterAction").startTimer(); + try { + Events.fireAfterAction(this); + } finally { + afterActionTimer.observeDuration(); + } + } + } + + Summary.Timer finalizeActionTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "finalizeAction").startTimer(); + try { + finalizeAction(); + } catch (InterruptException ignored) { + // No operations. + } finally { + finalizeActionTimer.observeDuration(); + } + + if (!isSkipTemplate()) { + Map params = new HashMap<>(internalGetTemplateMap()); + params.putAll(internalGetGlobalTemplateMap()); + + Summary.Timer templateTimer = Prometheus.getPagesLatencySeconds() + .labels(simpleClassName, "template").startTimer(); + try { + getTemplate().setOutputEncoding(StandardCharsets.UTF_8.name()); + + StringWriter stringWriter = new StringWriter(65536); + getTemplate().process(params, stringWriter); + stringWriter.close(); + + result = ((FrameDirective) getGlobalTemplateMap().get("frame")).processComponentUniques( + new StringBuilder(stringWriter.getBuffer())); + + if (cacheHandler != null) { + cacheHandler.postprocess(this, result); + } + + result = handleRequestPostprocessor(result); + } catch (TemplateException e) { + throw new FreemarkerException("Can't parse template for page " + getClass().getName() + '.', e); + } catch (IOException e) { + if (!e.toString().contains("ClientAbortException")) { + throw new FreemarkerException("Can't parse template for page " + getClass().getName() + '.', e); + } + } finally { + templateTimer.observeDuration(); + } + } + } + + if (result != null) { + getOutputStream().write(result.getBytes(StandardCharsets.UTF_8)); + } + } catch (AbortException ignored) { + // No operations. + } catch (IOException e) { + throw new FreemarkerException("Can't write page " + getClass().getName() + '.', e); + } finally { + finalizeAfterAction(); + overallTimer.observeDuration(); + } + } + + void finalizeAfterAction() { + requestCache = null; + globalTemplateMap = null; + super.finalizeAfterAction(); + } + + private String handleRequestPostprocessor(String result) { + ResponsePostprocessor postprocessor = responsePostprocessor; + if (postprocessor != null) { + result = postprocessor.postprocess(this, result); + } + return result; + } + + @Override + void prepareForAction() { + jsSet.clear(); + cssSet.clear(); + + setupCurrentPage(); + + globalTemplateMap = Collections.synchronizedMap(new HashMap<>()); + requestCache = Collections.synchronizedMap(new HashMap<>()); + + super.prepareForAction(); + + internalGetGlobalTemplateMap().put("frame", new FrameDirective()); + internalGetGlobalTemplateMap().put("once", new OnceDirective()); + + internalGetTemplateMap().put("css", cssSet); + internalGetTemplateMap().put("js", jsSet); + + processChain = false; + } + + @Override + protected final void setupRequestParams() { + setupRequestParamsForPage(); + } + + private void setupCurrentPage() { + if (ApplicationContext.getInstance().isDebug()) { + try { + ReflectionUtil.invoke(ApplicationContext.getInstance(), "setCurrentPage", this); + } catch (ReflectionException e) { + throw new NocturneException("Can't set current page.", e); + } + } else { + ApplicationContext.getInstance().setCurrentPage(this); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/PageLoader.java b/code/src/main/java/org/nocturne/main/PageLoader.java index 86ffa08..9ef6d67 100644 --- a/code/src/main/java/org/nocturne/main/PageLoader.java +++ b/code/src/main/java/org/nocturne/main/PageLoader.java @@ -1,124 +1,124 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.apache.log4j.Logger; -import org.nocturne.exception.ConfigurationException; -import org.nocturne.pool.PagePool; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Loads pages from the pool. - * - * @author Mike Mirzayanov - */ -@SuppressWarnings("WeakerAccess") -public class PageLoader { - private static final Logger logger = Logger.getLogger(PageLoader.class); - - private RequestRouter requestRouter; - private final ConcurrentMap pagePoolMap = new ConcurrentHashMap<>(); - - private static final Lock lock = new ReentrantLock(); - - private volatile boolean initialized; - - void initialize() { - if (!initialized) { - lock.lock(); - try { - if (requestRouter == null) { - try { - requestRouter = (RequestRouter) getClass().getClassLoader().loadClass( - ApplicationContext.getInstance().getRequestRouter() - ).getConstructor().newInstance(); - } catch (NoSuchMethodException e) { - logger.error( - "Application page class name resolver does not have default constructor.", e); - throw new ConfigurationException( - "Application page class name resolver does not have default constructor.", e); - } catch (Exception e) { - logger.error("Can't load application page class name resolver.", e); - throw new ConfigurationException("Can't load application page class name resolver.", e); - } - } - initialized = true; - } finally { - lock.unlock(); - logger.debug("Page loader has been initialized."); - } - } - } - - public Page loadPage(String path, Map> parameterMap) { - initialize(); - - RequestRouter.Resolution resolution = requestRouter.route(path, parameterMap); - if (resolution == null && ApplicationContext.getInstance().getDefaultPageClassName() != null) { - resolution = new RequestRouter.Resolution(ApplicationContext.getInstance().getDefaultPageClassName(), ""); - } - - if (resolution == null) { - return null; - } else { - ApplicationContext.getInstance().setRequestAction(resolution.getAction()); - ApplicationContext.getInstance().setRequestPageClassName(resolution.getPageClassName()); - - Map> overrideParameters = resolution.getOverrideParameters(); - if (overrideParameters != null) { - for (Map.Entry> entry : overrideParameters.entrySet()) { - ApplicationContext.getInstance().addRequestOverrideParameter(entry.getKey(), entry.getValue()); - } - } - - PagePool pool = getPoolByClassName(resolution.getPageClassName()); - return pool.getInstance(); - } - } - - @SuppressWarnings({"UnusedDeclaration"}) - public void unloadPage(String path, Map> parameterMap, Page page) { - String className = ApplicationContext.getInstance().getRequestPageClassName(); - PagePool pool = getPoolByClassName(className); - pool.release(page); - } - - private PagePool getPoolByClassName(String className) { - PagePool pool = pagePoolMap.get(className); - - if (pool == null) { - pagePoolMap.putIfAbsent(className, new PagePool(this, className)); - pool = pagePoolMap.get(className); - } - - return pool; - } - - @SuppressWarnings({"unchecked", "MethodMayBeStatic"}) - public Page loadPage(String pageClassName) { - try { - Class pageClass = (Class) PageLoader.class.getClassLoader().loadClass(pageClassName); - return ApplicationContext.getInstance().getInjector().getInstance(pageClass); - } catch (Exception e) { - logger.error("Can't load page " + pageClassName + '.', e); - throw new ConfigurationException("Can't load page " + pageClassName + '.', e); - } - } - - public void close() { - Collection values = pagePoolMap.values(); - PagePool[] pools = values.toArray(new PagePool[0]); - - for (PagePool pool : pools) { - pool.close(); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.apache.log4j.Logger; +import org.nocturne.exception.ConfigurationException; +import org.nocturne.pool.PagePool; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Loads pages from the pool. + * + * @author Mike Mirzayanov + */ +@SuppressWarnings("WeakerAccess") +public class PageLoader { + private static final Logger logger = Logger.getLogger(PageLoader.class); + + private RequestRouter requestRouter; + private final ConcurrentMap pagePoolMap = new ConcurrentHashMap<>(); + + private static final Lock lock = new ReentrantLock(); + + private volatile boolean initialized; + + void initialize() { + if (!initialized) { + lock.lock(); + try { + if (requestRouter == null) { + try { + requestRouter = (RequestRouter) getClass().getClassLoader().loadClass( + ApplicationContext.getInstance().getRequestRouter() + ).getConstructor().newInstance(); + } catch (NoSuchMethodException e) { + logger.error( + "Application page class name resolver does not have default constructor.", e); + throw new ConfigurationException( + "Application page class name resolver does not have default constructor.", e); + } catch (Exception e) { + logger.error("Can't load application page class name resolver.", e); + throw new ConfigurationException("Can't load application page class name resolver.", e); + } + } + initialized = true; + } finally { + lock.unlock(); + logger.debug("Page loader has been initialized."); + } + } + } + + public Page loadPage(String path, Map> parameterMap) { + initialize(); + + RequestRouter.Resolution resolution = requestRouter.route(path, parameterMap); + if (resolution == null && ApplicationContext.getInstance().getDefaultPageClassName() != null) { + resolution = new RequestRouter.Resolution(ApplicationContext.getInstance().getDefaultPageClassName(), ""); + } + + if (resolution == null) { + return null; + } else { + ApplicationContext.getInstance().setRequestAction(resolution.getAction()); + ApplicationContext.getInstance().setRequestPageClassName(resolution.getPageClassName()); + + Map> overrideParameters = resolution.getOverrideParameters(); + if (overrideParameters != null) { + for (Map.Entry> entry : overrideParameters.entrySet()) { + ApplicationContext.getInstance().addRequestOverrideParameter(entry.getKey(), entry.getValue()); + } + } + + PagePool pool = getPoolByClassName(resolution.getPageClassName()); + return pool.getInstance(); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + public void unloadPage(String path, Map> parameterMap, Page page) { + String className = ApplicationContext.getInstance().getRequestPageClassName(); + PagePool pool = getPoolByClassName(className); + pool.release(page); + } + + private PagePool getPoolByClassName(String className) { + PagePool pool = pagePoolMap.get(className); + + if (pool == null) { + pagePoolMap.putIfAbsent(className, new PagePool(this, className)); + pool = pagePoolMap.get(className); + } + + return pool; + } + + @SuppressWarnings({"unchecked", "MethodMayBeStatic"}) + public Page loadPage(String pageClassName) { + try { + Class pageClass = (Class) PageLoader.class.getClassLoader().loadClass(pageClassName); + return ApplicationContext.getInstance().getInjector().getInstance(pageClass); + } catch (Exception e) { + logger.error("Can't load page " + pageClassName + '.', e); + throw new ConfigurationException("Can't load page " + pageClassName + '.', e); + } + } + + public void close() { + Collection values = pagePoolMap.values(); + PagePool[] pools = values.toArray(new PagePool[0]); + + for (PagePool pool : pools) { + pool.close(); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/ParametersInjector.java b/code/src/main/java/org/nocturne/main/ParametersInjector.java index 75cc82f..bdb96f3 100644 --- a/code/src/main/java/org/nocturne/main/ParametersInjector.java +++ b/code/src/main/java/org/nocturne/main/ParametersInjector.java @@ -1,430 +1,430 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import com.google.common.base.Preconditions; -import net.sf.cglib.reflect.FastMethod; -import org.apache.log4j.Logger; -import org.jetbrains.annotations.Contract; -import org.nocturne.annotation.Parameter; -import org.nocturne.exception.ConfigurationException; -import org.nocturne.exception.NocturneException; -import org.nocturne.util.RequestUtil; -import org.nocturne.util.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.util.*; -import java.util.regex.Pattern; - -/** - *

- * Each component has an private instance of ParametersInjector. - * It will process @Parameter annotations. - *

- *

- * Also this class can be used if you want to - * inject parameters into some object. - *

- * - * @author Mike Mirzayanov - */ -@SuppressWarnings("WeakerAccess") -public class ParametersInjector { - private static final Logger logger = Logger.getLogger(ParametersInjector.class); - - private static final Pattern INTEGRAL_VALUE_PATTERN = Pattern.compile("0|(-?[1-9][0-9]*)"); - private static final Pattern REAL_VALUE_PATTERN = Pattern.compile("(0|(-?[1-9][0-9]*))((\\.[0-9]+)?)"); - - private static final Character NULL_ASSIGN_CHAR = 0; - private static final Byte NULL_ASSIGN_BYTE = 0; - private static final Short NULL_ASSIGN_SHORT = 0; - private static final Integer NULL_ASSIGN_INT = 0; - private static final Long NULL_ASSIGN_LONG = 0L; - private static final Float NULL_ASSIGN_FLOAT = 0.0F; - private static final Double NULL_ASSIGN_DOUBLE = 0.0D; - - /** - * Injection target object. - */ - private final Object component; - - /** - * Stores information about - */ - private Set fields; - - /** - * @param component Object which has fields with @Parameter annotation. - */ - public ParametersInjector(Object component) { - this.component = component; - } - - /** - * @param request Request to be analyzed to find parameters for injection. - * Also more priority parameters are retrieved from ApplicationContext.getInstance().getRequestOverrideParameters(). - */ - public void inject(HttpServletRequest request) { - if (fields == null) { - scanFields(); - } - - setupFields(request, fields); - } - - /** - * Returns parameter values, all parameters expected to be annotated with named @Parameter. - * - * @param request Http request. - * @param method Method which parameters will be analyzed to assign values. - * @return Object[] containing values for method parameters from the http request. - */ - Object[] setupParameters(HttpServletRequest request, FastMethod method) { - Class[] parameterTypes = method.getParameterTypes(); - Annotation[][] parameterAnnotations = method.getJavaMethod().getParameterAnnotations(); - - if (parameterTypes.length != parameterAnnotations.length) { - logger.error("Expected the same number of parameters and annotations."); - throw new NocturneException("Expected the same number of parameters and annotations."); - } - - List injectFields = new ArrayList<>(parameterTypes.length); - - for (int i = 0; i < parameterTypes.length; ++i) { - Class parameterType = parameterTypes[i]; - Parameter parameter = null; - for (int j = 0; j < parameterAnnotations[i].length; ++j) { - if (parameterAnnotations[i][j] instanceof Parameter) { - parameter = (Parameter) parameterAnnotations[i][j]; - } - } - if (parameter == null) { - 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 (StringUtil.isEmpty(parameter.name())) { - 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."); - } - InjectField injectField = new InjectField(null, parameter); - injectField.nonFieldType = parameterType; - injectFields.add(injectField); - } - - setupFields(request, injectFields); - - Object[] result = new Object[injectFields.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = injectFields.get(i).nonFieldValue; - } - - return result; - } - - private void setupFields(HttpServletRequest request, Collection fields) { - Map> overrideParameters = - ApplicationContext.getInstance().getRequestOverrideParameters(); - Map> requestParameters = RequestUtil.getRequestParams(request); - - for (InjectField field : fields) { - String key = field.parameter.name().isEmpty() - ? Preconditions.checkNotNull(field.field).getName() : field.parameter.name(); - - List values; - if (overrideParameters != null && overrideParameters.containsKey(key)) { - values = overrideParameters.get(key); - } else { - values = requestParameters.get(key); - } - - setupField(field, values); - } - - if (component instanceof Component) { - Component comp = (Component) component; - if (overrideParameters != null) { - for (Map.Entry> entry : overrideParameters.entrySet()) { - comp.addOverrideParameter(entry.getKey(), entry.getValue()); - } - } - } - } - - private void setupField(InjectField field, @Nullable List values) { - Class fieldType = getFieldType(field); - - if (fieldType.isArray()) { - setFieldValue(field, getArrayAssignValue(field, values, fieldType)); - } else { - String value = RequestUtil.getFirst(values); - - if (value == null) { - setFieldValue(field, getNullAssignValue(fieldType)); - } else { - value = field.parameter.stripMode().strip(value); - setFieldValue(field, getAssignValue(field, value, fieldType)); - } - } - } - - static Object getArrayAssignValue( - @Nullable InjectField field, @Nullable List values, Class fieldType) { - Class componentType = fieldType.getComponentType(); - - if (componentType.isArray()) { - throw getIllegalFieldTypeException(field, fieldType); - } - - int valueCount = values == null || values.isEmpty() ? 0 : values.size(); - Object fieldValue = Array.newInstance(componentType, valueCount); - - for (int valueIndex = 0; valueIndex < valueCount; ++valueIndex) { - String value = values.get(valueIndex); - - if (value == null) { - Array.set(fieldValue, valueIndex, getNullAssignValue(componentType)); - } else { - if (field != null) { - value = field.parameter.stripMode().strip(value); - } - Array.set(fieldValue, valueIndex, getAssignValue(field, value, componentType)); - } - } - - return fieldValue; - } - - @Contract(pure = true) - @Nullable - private static Object getNullAssignValue(Class fieldType) { - if (fieldType == boolean.class) { - return Boolean.FALSE; - } - - if (fieldType == char.class) { - return NULL_ASSIGN_CHAR; - } - - if (fieldType == byte.class) { - return NULL_ASSIGN_BYTE; - } - - if (fieldType == short.class) { - return NULL_ASSIGN_SHORT; - } - - if (fieldType == int.class) { - return NULL_ASSIGN_INT; - } - - if (fieldType == long.class) { - return NULL_ASSIGN_LONG; - } - - if (fieldType == float.class) { - return NULL_ASSIGN_FLOAT; - } - - if (fieldType == double.class) { - return NULL_ASSIGN_DOUBLE; - } - - return null; - } - - @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"}) - @Nullable - private static Object getAssignValue(@Nullable InjectField field, String value, @Nonnull Class targetType) { - if (targetType.equals(String.class)) { - return value; - } - - if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) { - if ("true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value) - || "yes".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value) - || "y".equalsIgnoreCase(value) || "checked".equalsIgnoreCase(value)) { - return Boolean.TRUE; - } else { - try { - return Boolean.valueOf(value); - } catch (RuntimeException ignored) { - return Boolean.FALSE; - } - } - } - - if (targetType.equals(Character.class) || targetType.equals(char.class)) { - try { - if (value.isEmpty()) { - return NULL_ASSIGN_CHAR; - } else { - return value.charAt(0); - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_CHAR; - } - } - - if (targetType.equals(Byte.class) || targetType.equals(byte.class)) { - try { - if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { - return Byte.valueOf(value); - } else { - return NULL_ASSIGN_BYTE; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_BYTE; - } - } - - if (targetType.equals(Short.class) || targetType.equals(short.class)) { - try { - if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { - return Short.valueOf(value); - } else { - return NULL_ASSIGN_SHORT; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_SHORT; - } - } - - if (targetType.equals(Integer.class) || targetType.equals(int.class)) { - try { - if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { - return Integer.valueOf(value); - } else { - return NULL_ASSIGN_INT; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_INT; - } - } - - if (targetType.equals(Long.class) || targetType.equals(long.class)) { - try { - if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { - return Long.valueOf(value); - } else { - return NULL_ASSIGN_LONG; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_LONG; - } - } - - if (targetType.equals(Float.class) || targetType.equals(float.class)) { - try { - if (REAL_VALUE_PATTERN.matcher(value).matches()) { - return Float.valueOf(value); - } else { - return NULL_ASSIGN_FLOAT; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_FLOAT; - } - } - - if (targetType.equals(Double.class) || targetType.equals(double.class)) { - try { - if (REAL_VALUE_PATTERN.matcher(value).matches()) { - return Double.valueOf(value); - } else { - return NULL_ASSIGN_DOUBLE; - } - } catch (RuntimeException ignored) { - return NULL_ASSIGN_DOUBLE; - } - } - - if (targetType.isEnum()) { - for (Object constName : targetType.getEnumConstants()) { - if (constName.toString().equalsIgnoreCase(value)) { - return constName; - } - } - return null; - } - - throw getIllegalFieldTypeException(field, targetType); - } - - @Contract(pure = true) - private static Class getFieldType(InjectField field) { - return field.field == null ? field.nonFieldType : field.field.getType(); - } - - private void setFieldValue(InjectField field, @Nullable Object assign) { - if (field.field == null) { - field.nonFieldValue = assign; - } else { - field.field.setAccessible(true); - try { - field.field.set(component, assign); - } catch (IllegalAccessException e) { - String message = String.format( - "Don't have access to set field %s of %s.", - field.field.getName(), field.field.getDeclaringClass().getName() - ); - logger.error(message, e); - throw new IllegalArgumentException(message, e); - } - } - } - - private void scanFields() { - fields = new HashSet<>(); - Class clazz = component.getClass(); - - while (clazz != null) { - Field[] clazzFields = clazz.getDeclaredFields(); - - for (Field clazzField : clazzFields) { - Parameter parameter = clazzField.getAnnotation(Parameter.class); - if (parameter != null) { - fields.add(new InjectField(clazzField, parameter)); - } - } - - clazz = clazz.getSuperclass(); - } - } - - @Nonnull - private static ConfigurationException getIllegalFieldTypeException( - @Nullable InjectField field, @Nonnull Class fieldType) { - if (field == null || field.field == null) { - return new ConfigurationException(String.format("Field has unexpected type %s.", fieldType.getName())); - } else { - return new ConfigurationException(String.format( - "Field %s of %s has unexpected type %s.", - field.field.getName(), field.field.getDeclaringClass().getName(), fieldType.getName() - )); - } - } - - @SuppressWarnings("PackageVisibleField") - private static class InjectField { - @Nullable - final Field field; - final Parameter parameter; - - Object nonFieldValue; - Class nonFieldType; - - private InjectField(@Nullable Field field, Parameter parameter) { - this.field = field; - this.parameter = parameter; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import com.google.common.base.Preconditions; +import net.sf.cglib.reflect.FastMethod; +import org.apache.log4j.Logger; +import org.jetbrains.annotations.Contract; +import org.nocturne.annotation.Parameter; +import org.nocturne.exception.ConfigurationException; +import org.nocturne.exception.NocturneException; +import org.nocturne.util.RequestUtil; +import org.nocturne.util.StringUtil; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.*; +import java.util.regex.Pattern; + +/** + *

+ * Each component has an private instance of ParametersInjector. + * It will process @Parameter annotations. + *

+ *

+ * Also this class can be used if you want to + * inject parameters into some object. + *

+ * + * @author Mike Mirzayanov + */ +@SuppressWarnings("WeakerAccess") +public class ParametersInjector { + private static final Logger logger = Logger.getLogger(ParametersInjector.class); + + private static final Pattern INTEGRAL_VALUE_PATTERN = Pattern.compile("0|(-?[1-9][0-9]*)"); + private static final Pattern REAL_VALUE_PATTERN = Pattern.compile("(0|(-?[1-9][0-9]*))((\\.[0-9]+)?)"); + + private static final Character NULL_ASSIGN_CHAR = 0; + private static final Byte NULL_ASSIGN_BYTE = 0; + private static final Short NULL_ASSIGN_SHORT = 0; + private static final Integer NULL_ASSIGN_INT = 0; + private static final Long NULL_ASSIGN_LONG = 0L; + private static final Float NULL_ASSIGN_FLOAT = 0.0F; + private static final Double NULL_ASSIGN_DOUBLE = 0.0D; + + /** + * Injection target object. + */ + private final Object component; + + /** + * Stores information about + */ + private Set fields; + + /** + * @param component Object which has fields with @Parameter annotation. + */ + public ParametersInjector(Object component) { + this.component = component; + } + + /** + * @param request Request to be analyzed to find parameters for injection. + * Also more priority parameters are retrieved from ApplicationContext.getInstance().getRequestOverrideParameters(). + */ + public void inject(HttpServletRequest request) { + if (fields == null) { + scanFields(); + } + + setupFields(request, fields); + } + + /** + * Returns parameter values, all parameters expected to be annotated with named @Parameter. + * + * @param request Http request. + * @param method Method which parameters will be analyzed to assign values. + * @return Object[] containing values for method parameters from the http request. + */ + Object[] setupParameters(HttpServletRequest request, FastMethod method) { + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getJavaMethod().getParameterAnnotations(); + + if (parameterTypes.length != parameterAnnotations.length) { + logger.error("Expected the same number of parameters and annotations."); + throw new NocturneException("Expected the same number of parameters and annotations."); + } + + List injectFields = new ArrayList<>(parameterTypes.length); + + for (int i = 0; i < parameterTypes.length; ++i) { + Class parameterType = parameterTypes[i]; + Parameter parameter = null; + for (int j = 0; j < parameterAnnotations[i].length; ++j) { + if (parameterAnnotations[i][j] instanceof Parameter) { + parameter = (Parameter) parameterAnnotations[i][j]; + } + } + if (parameter == null) { + 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 (StringUtil.isEmpty(parameter.name())) { + 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."); + } + InjectField injectField = new InjectField(null, parameter); + injectField.nonFieldType = parameterType; + injectFields.add(injectField); + } + + setupFields(request, injectFields); + + Object[] result = new Object[injectFields.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = injectFields.get(i).nonFieldValue; + } + + return result; + } + + private void setupFields(HttpServletRequest request, Collection fields) { + Map> overrideParameters = + ApplicationContext.getInstance().getRequestOverrideParameters(); + Map> requestParameters = RequestUtil.getRequestParams(request); + + for (InjectField field : fields) { + String key = field.parameter.name().isEmpty() + ? Preconditions.checkNotNull(field.field).getName() : field.parameter.name(); + + List values; + if (overrideParameters != null && overrideParameters.containsKey(key)) { + values = overrideParameters.get(key); + } else { + values = requestParameters.get(key); + } + + setupField(field, values); + } + + if (component instanceof Component) { + Component comp = (Component) component; + if (overrideParameters != null) { + for (Map.Entry> entry : overrideParameters.entrySet()) { + comp.addOverrideParameter(entry.getKey(), entry.getValue()); + } + } + } + } + + private void setupField(InjectField field, @Nullable List values) { + Class fieldType = getFieldType(field); + + if (fieldType.isArray()) { + setFieldValue(field, getArrayAssignValue(field, values, fieldType)); + } else { + String value = RequestUtil.getFirst(values); + + if (value == null) { + setFieldValue(field, getNullAssignValue(fieldType)); + } else { + value = field.parameter.stripMode().strip(value); + setFieldValue(field, getAssignValue(field, value, fieldType)); + } + } + } + + static Object getArrayAssignValue( + @Nullable InjectField field, @Nullable List values, Class fieldType) { + Class componentType = fieldType.getComponentType(); + + if (componentType.isArray()) { + throw getIllegalFieldTypeException(field, fieldType); + } + + int valueCount = values == null || values.isEmpty() ? 0 : values.size(); + Object fieldValue = Array.newInstance(componentType, valueCount); + + for (int valueIndex = 0; valueIndex < valueCount; ++valueIndex) { + String value = values.get(valueIndex); + + if (value == null) { + Array.set(fieldValue, valueIndex, getNullAssignValue(componentType)); + } else { + if (field != null) { + value = field.parameter.stripMode().strip(value); + } + Array.set(fieldValue, valueIndex, getAssignValue(field, value, componentType)); + } + } + + return fieldValue; + } + + @Contract(pure = true) + @Nullable + private static Object getNullAssignValue(Class fieldType) { + if (fieldType == boolean.class) { + return Boolean.FALSE; + } + + if (fieldType == char.class) { + return NULL_ASSIGN_CHAR; + } + + if (fieldType == byte.class) { + return NULL_ASSIGN_BYTE; + } + + if (fieldType == short.class) { + return NULL_ASSIGN_SHORT; + } + + if (fieldType == int.class) { + return NULL_ASSIGN_INT; + } + + if (fieldType == long.class) { + return NULL_ASSIGN_LONG; + } + + if (fieldType == float.class) { + return NULL_ASSIGN_FLOAT; + } + + if (fieldType == double.class) { + return NULL_ASSIGN_DOUBLE; + } + + return null; + } + + @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"}) + @Nullable + private static Object getAssignValue(@Nullable InjectField field, String value, @Nonnull Class targetType) { + if (targetType.equals(String.class)) { + return value; + } + + if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) { + if ("true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value) + || "yes".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value) + || "y".equalsIgnoreCase(value) || "checked".equalsIgnoreCase(value)) { + return Boolean.TRUE; + } else { + try { + return Boolean.valueOf(value); + } catch (RuntimeException ignored) { + return Boolean.FALSE; + } + } + } + + if (targetType.equals(Character.class) || targetType.equals(char.class)) { + try { + if (value.isEmpty()) { + return NULL_ASSIGN_CHAR; + } else { + return value.charAt(0); + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_CHAR; + } + } + + if (targetType.equals(Byte.class) || targetType.equals(byte.class)) { + try { + if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { + return Byte.valueOf(value); + } else { + return NULL_ASSIGN_BYTE; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_BYTE; + } + } + + if (targetType.equals(Short.class) || targetType.equals(short.class)) { + try { + if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { + return Short.valueOf(value); + } else { + return NULL_ASSIGN_SHORT; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_SHORT; + } + } + + if (targetType.equals(Integer.class) || targetType.equals(int.class)) { + try { + if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { + return Integer.valueOf(value); + } else { + return NULL_ASSIGN_INT; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_INT; + } + } + + if (targetType.equals(Long.class) || targetType.equals(long.class)) { + try { + if (INTEGRAL_VALUE_PATTERN.matcher(value).matches()) { + return Long.valueOf(value); + } else { + return NULL_ASSIGN_LONG; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_LONG; + } + } + + if (targetType.equals(Float.class) || targetType.equals(float.class)) { + try { + if (REAL_VALUE_PATTERN.matcher(value).matches()) { + return Float.valueOf(value); + } else { + return NULL_ASSIGN_FLOAT; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_FLOAT; + } + } + + if (targetType.equals(Double.class) || targetType.equals(double.class)) { + try { + if (REAL_VALUE_PATTERN.matcher(value).matches()) { + return Double.valueOf(value); + } else { + return NULL_ASSIGN_DOUBLE; + } + } catch (RuntimeException ignored) { + return NULL_ASSIGN_DOUBLE; + } + } + + if (targetType.isEnum()) { + for (Object constName : targetType.getEnumConstants()) { + if (constName.toString().equalsIgnoreCase(value)) { + return constName; + } + } + return null; + } + + throw getIllegalFieldTypeException(field, targetType); + } + + @Contract(pure = true) + private static Class getFieldType(InjectField field) { + return field.field == null ? field.nonFieldType : field.field.getType(); + } + + private void setFieldValue(InjectField field, @Nullable Object assign) { + if (field.field == null) { + field.nonFieldValue = assign; + } else { + field.field.setAccessible(true); + try { + field.field.set(component, assign); + } catch (IllegalAccessException e) { + String message = String.format( + "Don't have access to set field %s of %s.", + field.field.getName(), field.field.getDeclaringClass().getName() + ); + logger.error(message, e); + throw new IllegalArgumentException(message, e); + } + } + } + + private void scanFields() { + fields = new HashSet<>(); + Class clazz = component.getClass(); + + while (clazz != null) { + Field[] clazzFields = clazz.getDeclaredFields(); + + for (Field clazzField : clazzFields) { + Parameter parameter = clazzField.getAnnotation(Parameter.class); + if (parameter != null) { + fields.add(new InjectField(clazzField, parameter)); + } + } + + clazz = clazz.getSuperclass(); + } + } + + @Nonnull + private static ConfigurationException getIllegalFieldTypeException( + @Nullable InjectField field, @Nonnull Class fieldType) { + if (field == null || field.field == null) { + return new ConfigurationException(String.format("Field has unexpected type %s.", fieldType.getName())); + } else { + return new ConfigurationException(String.format( + "Field %s of %s has unexpected type %s.", + field.field.getName(), field.field.getDeclaringClass().getName(), fieldType.getName() + )); + } + } + + @SuppressWarnings("PackageVisibleField") + private static class InjectField { + @Nullable + final Field field; + final Parameter parameter; + + Object nonFieldValue; + Class nonFieldType; + + private InjectField(@Nullable Field field, Parameter parameter) { + this.field = field; + this.parameter = parameter; + } + } +} diff --git a/code/src/main/java/org/nocturne/main/ReloadingClassLoader.java b/code/src/main/java/org/nocturne/main/ReloadingClassLoader.java index ed9639a..ffb2c50 100644 --- a/code/src/main/java/org/nocturne/main/ReloadingClassLoader.java +++ b/code/src/main/java/org/nocturne/main/ReloadingClassLoader.java @@ -1,134 +1,134 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Mike Mirzayanov - */ -@SuppressWarnings("WeakerAccess") -class ReloadingClassLoader extends ClassLoader { - /** - * Standard class loader class path. - */ - private static final URL[] classPathUrls = ((URLClassLoader) ReloadingClassLoader.class.getClassLoader()).getURLs(); - - /** - * Class loader for delegation. - */ - private final DelegationClassLoader delegationClassLoader; - - /** - * Creates new instance of ReloadingClassLoader. - */ - public ReloadingClassLoader() { - List delegationClassLoaderClassPath = new ArrayList<>(); - List classPathItems = ReloadingContext.getInstance().getReloadingClassPaths(); - - for (File classPathDir : classPathItems) { - if (classPathDir.isDirectory()) { - try { - delegationClassLoaderClassPath.add(classPathDir.toURI().toURL()); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("The path " + classPathDir.getAbsolutePath() + " is not valid URL.", e); - } - } else { - throw new IllegalArgumentException("Expected to find directory for the path " + classPathDir.getName() + '.'); - } - } - - delegationClassLoaderClassPath.addAll(Arrays.asList(classPathUrls)); - - delegationClassLoader = new DelegationClassLoader(delegationClassLoaderClassPath.toArray(new URL[0])); - } - - /** - * @return Delegation classLoader which actually loads classes to be hot-swapped. - */ - DelegationClassLoader getDelegationClassLoader() { - return delegationClassLoader; - } - - /** - * Loads class. - * - * @param name Class name. - * @param resolve Resolve. - * @return Class Loaded class. - */ - @Override - public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return delegationClassLoader.loadClass(name, resolve); - } - - /** - * Load class using standard loader. - * - * @param name Class name. - * @param resolve Resolved. - * @return Class Loaded class. - * @throws ClassNotFoundException when Can't load class. - */ - @SuppressWarnings({"UnusedDeclaration"}) - private static Class loadUsingStandardClassLoader(String name, boolean resolve) throws ClassNotFoundException { - return Thread.currentThread().getContextClassLoader().loadClass(name); - } - - /** - * @param name Class name. - * @return boolean {@code true} iff this class should be loaded by standard class. - */ - private static boolean isForceToLoadUsingStandardClassLoader(String name) { - boolean reload = false; - - String nameWithDot = name + '.'; - String nameWithDollar = name + '$'; - - // Check if it is in the reloading packages. - List classReloadingPackages = ReloadingContext.getInstance().getClassReloadingPackages(); - for (String classReloadingPackage : classReloadingPackages) { - if (nameWithDot.startsWith(classReloadingPackage + '.')) { - reload = true; - break; - } - } - - // Check if it is in exceptions. - if (reload) { - List exceptions = new LinkedList<>(ReloadingContext.getInstance().getClassReloadingExceptions()); - for (String exception : exceptions) { - if (nameWithDot.startsWith(exception + '.') || nameWithDollar.startsWith(exception + '$')) { - reload = false; - break; - } - } - } - - return !reload; - } - - class DelegationClassLoader extends URLClassLoader { - public DelegationClassLoader(URL[] urls) { - super(urls); - } - - @Override - protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - // Use standard class loader? - if (isForceToLoadUsingStandardClassLoader(name)) { - return loadUsingStandardClassLoader(name, resolve); - } - - return super.loadClass(name, resolve); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Mike Mirzayanov + */ +@SuppressWarnings("WeakerAccess") +class ReloadingClassLoader extends ClassLoader { + /** + * Standard class loader class path. + */ + private static final URL[] classPathUrls = ((URLClassLoader) ReloadingClassLoader.class.getClassLoader()).getURLs(); + + /** + * Class loader for delegation. + */ + private final DelegationClassLoader delegationClassLoader; + + /** + * Creates new instance of ReloadingClassLoader. + */ + public ReloadingClassLoader() { + List delegationClassLoaderClassPath = new ArrayList<>(); + List classPathItems = ReloadingContext.getInstance().getReloadingClassPaths(); + + for (File classPathDir : classPathItems) { + if (classPathDir.isDirectory()) { + try { + delegationClassLoaderClassPath.add(classPathDir.toURI().toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("The path " + classPathDir.getAbsolutePath() + " is not valid URL.", e); + } + } else { + throw new IllegalArgumentException("Expected to find directory for the path " + classPathDir.getName() + '.'); + } + } + + delegationClassLoaderClassPath.addAll(Arrays.asList(classPathUrls)); + + delegationClassLoader = new DelegationClassLoader(delegationClassLoaderClassPath.toArray(new URL[0])); + } + + /** + * @return Delegation classLoader which actually loads classes to be hot-swapped. + */ + DelegationClassLoader getDelegationClassLoader() { + return delegationClassLoader; + } + + /** + * Loads class. + * + * @param name Class name. + * @param resolve Resolve. + * @return Class Loaded class. + */ + @Override + public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return delegationClassLoader.loadClass(name, resolve); + } + + /** + * Load class using standard loader. + * + * @param name Class name. + * @param resolve Resolved. + * @return Class Loaded class. + * @throws ClassNotFoundException when Can't load class. + */ + @SuppressWarnings({"UnusedDeclaration"}) + private static Class loadUsingStandardClassLoader(String name, boolean resolve) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass(name); + } + + /** + * @param name Class name. + * @return boolean {@code true} iff this class should be loaded by standard class. + */ + private static boolean isForceToLoadUsingStandardClassLoader(String name) { + boolean reload = false; + + String nameWithDot = name + '.'; + String nameWithDollar = name + '$'; + + // Check if it is in the reloading packages. + List classReloadingPackages = ReloadingContext.getInstance().getClassReloadingPackages(); + for (String classReloadingPackage : classReloadingPackages) { + if (nameWithDot.startsWith(classReloadingPackage + '.')) { + reload = true; + break; + } + } + + // Check if it is in exceptions. + if (reload) { + List exceptions = new LinkedList<>(ReloadingContext.getInstance().getClassReloadingExceptions()); + for (String exception : exceptions) { + if (nameWithDot.startsWith(exception + '.') || nameWithDollar.startsWith(exception + '$')) { + reload = false; + break; + } + } + } + + return !reload; + } + + class DelegationClassLoader extends URLClassLoader { + public DelegationClassLoader(URL[] urls) { + super(urls); + } + + @Override + protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + // Use standard class loader? + if (isForceToLoadUsingStandardClassLoader(name)) { + return loadUsingStandardClassLoader(name, resolve); + } + + return super.loadClass(name, resolve); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/ReloadingContext.java b/code/src/main/java/org/nocturne/main/ReloadingContext.java index 36bdd4a..4143231 100644 --- a/code/src/main/java/org/nocturne/main/ReloadingContext.java +++ b/code/src/main/java/org/nocturne/main/ReloadingContext.java @@ -1,118 +1,118 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.apache.log4j.Logger; -import org.nocturne.exception.ConfigurationException; - -import java.io.File; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Pattern; - -/** - * @author Mike Mirzayanov - */ -@SuppressWarnings({"unused", "WeakerAccess"}) -public class ReloadingContext { - private static final Logger logger = Logger.getLogger(ReloadingContext.class); - - private static final ReloadingContext INSTANCE = new ReloadingContext(); - private static final AtomicBoolean initialized = new AtomicBoolean(false); - - private volatile boolean running = true; - private boolean debug; - private Pattern skipRegex; - - private List reloadingClassPaths; - private List classReloadingPackages; - private List classReloadingExceptions; - - private int templatesUpdateDelay = 60; - - private ReloadingContext() { - } - - public static ReloadingContext getInstance() { - if (!initialized.getAndSet(true)) { - ReloadingContextLoader.run(); - } - - return INSTANCE; - } - - public boolean isDebug() { - return debug; - } - - /** - * @return {@code true} iff web application is not stopped - */ - public boolean isRunning() { - return running; - } - - void stop() { - this.running = false; - } - - public int getTemplatesUpdateDelay() { - return templatesUpdateDelay; - } - - public List getReloadingClassPaths() { - return Collections.unmodifiableList(reloadingClassPaths); - } - - public List getClassReloadingPackages() { - return Collections.unmodifiableList(classReloadingPackages); - } - - public List getClassReloadingExceptions() { - return Collections.unmodifiableList(classReloadingExceptions); - } - - void setDebug(boolean debug) { - this.debug = debug; - } - - void setSkipRegex(Pattern skipRegex) { - this.skipRegex = skipRegex; - } - - public Pattern getSkipRegex() { - return skipRegex; - } - - void setTemplatesUpdateDelay(int templatesUpdateDelay) { - this.templatesUpdateDelay = templatesUpdateDelay; - } - - void setReloadingClassPaths(List reloadingClassPaths) { - this.reloadingClassPaths = reloadingClassPaths; - } - - void setClassReloadingPackages(List classReloadingPackages) { - this.classReloadingPackages = classReloadingPackages; - } - - void setClassReloadingExceptions(List classReloadingExceptions) { - this.classReloadingExceptions = classReloadingExceptions; - } - - 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."); - } - if (!reloadingClassPaths.contains(dir)) { - reloadingClassPaths.add(dir); - } - } - - public void addClassReloadingException(String packageOrClassName) { - classReloadingExceptions.add(packageOrClassName); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.apache.log4j.Logger; +import org.nocturne.exception.ConfigurationException; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; + +/** + * @author Mike Mirzayanov + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public class ReloadingContext { + private static final Logger logger = Logger.getLogger(ReloadingContext.class); + + private static final ReloadingContext INSTANCE = new ReloadingContext(); + private static final AtomicBoolean initialized = new AtomicBoolean(false); + + private volatile boolean running = true; + private boolean debug; + private Pattern skipRegex; + + private List reloadingClassPaths; + private List classReloadingPackages; + private List classReloadingExceptions; + + private int templatesUpdateDelay = 60; + + private ReloadingContext() { + } + + public static ReloadingContext getInstance() { + if (!initialized.getAndSet(true)) { + ReloadingContextLoader.run(); + } + + return INSTANCE; + } + + public boolean isDebug() { + return debug; + } + + /** + * @return {@code true} iff web application is not stopped + */ + public boolean isRunning() { + return running; + } + + void stop() { + this.running = false; + } + + public int getTemplatesUpdateDelay() { + return templatesUpdateDelay; + } + + public List getReloadingClassPaths() { + return Collections.unmodifiableList(reloadingClassPaths); + } + + public List getClassReloadingPackages() { + return Collections.unmodifiableList(classReloadingPackages); + } + + public List getClassReloadingExceptions() { + return Collections.unmodifiableList(classReloadingExceptions); + } + + void setDebug(boolean debug) { + this.debug = debug; + } + + void setSkipRegex(Pattern skipRegex) { + this.skipRegex = skipRegex; + } + + public Pattern getSkipRegex() { + return skipRegex; + } + + void setTemplatesUpdateDelay(int templatesUpdateDelay) { + this.templatesUpdateDelay = templatesUpdateDelay; + } + + void setReloadingClassPaths(List reloadingClassPaths) { + this.reloadingClassPaths = reloadingClassPaths; + } + + void setClassReloadingPackages(List classReloadingPackages) { + this.classReloadingPackages = classReloadingPackages; + } + + void setClassReloadingExceptions(List classReloadingExceptions) { + this.classReloadingExceptions = classReloadingExceptions; + } + + 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."); + } + if (!reloadingClassPaths.contains(dir)) { + reloadingClassPaths.add(dir); + } + } + + public void addClassReloadingException(String packageOrClassName) { + classReloadingExceptions.add(packageOrClassName); + } +} diff --git a/code/src/main/java/org/nocturne/main/ReloadingContextLoader.java b/code/src/main/java/org/nocturne/main/ReloadingContextLoader.java index 09a7c81..f3daf0d 100644 --- a/code/src/main/java/org/nocturne/main/ReloadingContextLoader.java +++ b/code/src/main/java/org/nocturne/main/ReloadingContextLoader.java @@ -1,151 +1,151 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.apache.log4j.Logger; -import org.nocturne.exception.ConfigurationException; -import org.nocturne.prometheus.Prometheus; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * @author Mike Mirzayanov - */ -class ReloadingContextLoader { - private static final Logger logger = Logger.getLogger(ReloadingContextLoader.class); - - private static final Properties properties = new Properties(); - - static void run() { - setupDebug(); - setupTemplates(); - setupSkipRegex(); - - if (ReloadingContext.getInstance().isDebug()) { - setupReloadingClassPaths(); - setupClassReloadingPackages(); - setupClassReloadingExceptions(); - } - } - - 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."); - } - ReloadingContext.getInstance().setTemplatesUpdateDelay(templatesUpdateDelay); - } catch (NumberFormatException e) { - logger.error("Parameter nocturne.templates-update-delay should be integer."); - throw new ConfigurationException("Parameter nocturne.templates-update-delay should be integer."); - } - } - } - - private static void setupClassReloadingExceptions() { - List exceptions = new ArrayList<>(); - exceptions.add(ReloadingContext.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) { - String[] candidates = exceptionsAsString.split("\\s*;\\s*"); - for (String item : candidates) { - if (!item.isEmpty()) { - exceptions.add(item); - } - } - } - } - ReloadingContext.getInstance().setClassReloadingExceptions(exceptions); - } - - private static void setupSkipRegex() { - if (properties.containsKey("nocturne.skip-regex")) { - String regex = properties.getProperty("nocturne.skip-regex"); - if (regex != null && !regex.isEmpty()) { - try { - ReloadingContext.getInstance().setSkipRegex(Pattern.compile(regex)); - } catch (PatternSyntaxException e) { - logger.error("Parameter nocturne.skip-regex contains invalid pattern."); - throw new ConfigurationException("Parameter nocturne.skip-regex contains invalid pattern."); - } - } - } - } - - 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) { - String[] candidates = packagesAsString.split("\\s*;\\s*"); - for (String item : candidates) { - if (!item.isEmpty()) { - packages.add(item); - } - } - } - } - ReloadingContext.getInstance().setClassReloadingPackages(packages); - } - - 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 = reloadingClassPathsAsString.split("\\s*;\\s*"); - for (String dir : dirs) { - if (!dir.isEmpty()) { - File file = new File(dir); - if (!file.isDirectory() && ReloadingContext.getInstance().isDebug()) { - logger.error("Each item in nocturne.reloading-class-paths should be a directory," - + " but '" + file + "' isn't."); - throw new ConfigurationException("Each item in nocturne.reloading-class-paths should be a directory," - + " but '" + file + "' isn't."); - } - reloadingClassPaths.add(file); - } - } - } - } - ReloadingContext.getInstance().setReloadingClassPaths(reloadingClassPaths); - } - - private static void setupDebug() { - boolean debug = false; - - if (properties.containsKey("nocturne.debug")) { - try { - debug = Boolean.parseBoolean(properties.getProperty("nocturne.debug")); - } catch (NullPointerException e) { - logger.error("Can't cast nocturne.debug to boolean."); - throw new ConfigurationException("Can't cast nocturne.debug to boolean."); - } - } - - ReloadingContext.getInstance().setDebug(debug); - } - - static { - - try (InputStream inputStream = ApplicationContextLoader.class.getResourceAsStream(Constants.CONFIGURATION_FILE)) { - properties.load(inputStream); - } catch (IOException e) { - logger.error("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); - throw new ConfigurationException("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.apache.log4j.Logger; +import org.nocturne.exception.ConfigurationException; +import org.nocturne.prometheus.Prometheus; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * @author Mike Mirzayanov + */ +class ReloadingContextLoader { + private static final Logger logger = Logger.getLogger(ReloadingContextLoader.class); + + private static final Properties properties = new Properties(); + + static void run() { + setupDebug(); + setupTemplates(); + setupSkipRegex(); + + if (ReloadingContext.getInstance().isDebug()) { + setupReloadingClassPaths(); + setupClassReloadingPackages(); + setupClassReloadingExceptions(); + } + } + + 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."); + } + ReloadingContext.getInstance().setTemplatesUpdateDelay(templatesUpdateDelay); + } catch (NumberFormatException e) { + logger.error("Parameter nocturne.templates-update-delay should be integer."); + throw new ConfigurationException("Parameter nocturne.templates-update-delay should be integer."); + } + } + } + + private static void setupClassReloadingExceptions() { + List exceptions = new ArrayList<>(); + exceptions.add(ReloadingContext.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) { + String[] candidates = exceptionsAsString.split("\\s*;\\s*"); + for (String item : candidates) { + if (!item.isEmpty()) { + exceptions.add(item); + } + } + } + } + ReloadingContext.getInstance().setClassReloadingExceptions(exceptions); + } + + private static void setupSkipRegex() { + if (properties.containsKey("nocturne.skip-regex")) { + String regex = properties.getProperty("nocturne.skip-regex"); + if (regex != null && !regex.isEmpty()) { + try { + ReloadingContext.getInstance().setSkipRegex(Pattern.compile(regex)); + } catch (PatternSyntaxException e) { + logger.error("Parameter nocturne.skip-regex contains invalid pattern."); + throw new ConfigurationException("Parameter nocturne.skip-regex contains invalid pattern."); + } + } + } + } + + 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) { + String[] candidates = packagesAsString.split("\\s*;\\s*"); + for (String item : candidates) { + if (!item.isEmpty()) { + packages.add(item); + } + } + } + } + ReloadingContext.getInstance().setClassReloadingPackages(packages); + } + + 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 = reloadingClassPathsAsString.split("\\s*;\\s*"); + for (String dir : dirs) { + if (!dir.isEmpty()) { + File file = new File(dir); + if (!file.isDirectory() && ReloadingContext.getInstance().isDebug()) { + logger.error("Each item in nocturne.reloading-class-paths should be a directory," + + " but '" + file + "' isn't."); + throw new ConfigurationException("Each item in nocturne.reloading-class-paths should be a directory," + + " but '" + file + "' isn't."); + } + reloadingClassPaths.add(file); + } + } + } + } + ReloadingContext.getInstance().setReloadingClassPaths(reloadingClassPaths); + } + + private static void setupDebug() { + boolean debug = false; + + if (properties.containsKey("nocturne.debug")) { + try { + debug = Boolean.parseBoolean(properties.getProperty("nocturne.debug")); + } catch (NullPointerException e) { + logger.error("Can't cast nocturne.debug to boolean."); + throw new ConfigurationException("Can't cast nocturne.debug to boolean."); + } + } + + ReloadingContext.getInstance().setDebug(debug); + } + + static { + + try (InputStream inputStream = ApplicationContextLoader.class.getResourceAsStream(Constants.CONFIGURATION_FILE)) { + properties.load(inputStream); + } catch (IOException e) { + logger.error("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); + throw new ConfigurationException("Can't load resource file " + Constants.CONFIGURATION_FILE + '.', e); + } + } +} diff --git a/code/src/main/java/org/nocturne/main/RequestDispatcher.java b/code/src/main/java/org/nocturne/main/RequestDispatcher.java index 857a5f9..a512c40 100644 --- a/code/src/main/java/org/nocturne/main/RequestDispatcher.java +++ b/code/src/main/java/org/nocturne/main/RequestDispatcher.java @@ -1,343 +1,343 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import freemarker.template.Configuration; -import org.apache.log4j.Logger; -import org.nocturne.exception.NocturneException; -import org.nocturne.exception.ReflectionException; -import org.nocturne.listener.PageRequestListener; -import org.nocturne.pool.TemplateEngineConfigurationPool; -import org.nocturne.util.ReflectionUtil; -import org.nocturne.util.RequestUtil; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.Flushable; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * @author Mike Mirzayanov - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public class RequestDispatcher { - private static final Logger logger = Logger.getLogger(RequestDispatcher.class); - - private final ApplicationContext applicationContext = ApplicationContext.getInstance(); - - /** - * Freemarker configuration. - */ - private TemplateEngineConfigurationPool templateEngineConfigurationPool; - - /** - * Page loader for production mode. - */ - private final PageLoader pageLoader = new PageLoader(); - - /** - * Servlet config. - */ - private FilterConfig filterConfig; - - /** - * Listens requests for pages. - */ - private List pageRequestListeners; - - /** - * Class loader used when application has been accessed in the debug mode. - */ - private ClassLoader reloadingClassLoader; - - void setReloadingClassLoader(ClassLoader reloadingClassLoader) { - this.reloadingClassLoader = reloadingClassLoader; - } - - private static boolean isClientAbortException(Exception e) { - return e != null && (e.getClass().getName().contains("ClientAbortException") - || (e.getCause() != null && e.getCause().getClass().getName().contains("ClientAbortException"))); - } - - /** - * Run production mode request handling. - * - * @param request Request. - * @param response Response. - * @return Page run result. - * @throws IOException when Something wrong with IO. - */ - private RunResult runProductionService(HttpServletRequest request, HttpServletResponse response) throws IOException { - RunResult result = new RunResult(); - - String path = request.getServletPath(); - - Map> parameterMap = RequestUtil.getRequestParams(request); - Page page = pageLoader.loadPage(path, parameterMap); - - if (page == null) { - result.setProcessChain(true); - return result; - } - - Configuration templateEngineConfiguration = templateEngineConfigurationPool.getInstance(); - - boolean processChain = false; - Throwable pageThrowable = null; - - try { - page.setTemplateEngineConfiguration(templateEngineConfiguration); - page.setRequest(request); - page.setFilterConfig(filterConfig); - page.setResponse(response); - - setupPageRequestListener(page); - handleBeforeProcessPage(page); - - page.parseTemplate(); - processChain = page.isProcessChain(); - - page.getOutputStream().flush(); - page.getWriter().flush(); - } catch (Exception e) { - pageThrowable = e; - if (!isClientAbortException(e)) { - e.printStackTrace(System.err); - System.err.flush(); - e.printStackTrace(System.out); - System.out.flush(); - logger.fatal("Can't process " + request.getRequestURL() + '.', e); - } - } finally { - handleAfterProcessPage(page, pageThrowable); - pageLoader.unloadPage(path, parameterMap, page); - templateEngineConfigurationPool.release(templateEngineConfiguration); - } - - result.setProcessChain(processChain); - return result; - } - - private void setupPageRequestListener(Object page) throws ClassNotFoundException { - if (pageRequestListeners == null || applicationContext.isDebug()) { - pageRequestListeners = new CopyOnWriteArrayList<>(); - - for (String name : applicationContext.getPageRequestListeners()) { - ClassLoader loader = page.getClass().getClassLoader(); - Class clazz = loader.loadClass(name); - Object listener = applicationContext.getInjector().getInstance(clazz); - pageRequestListeners.add(listener); - } - } - } - - private void handleBeforeProcessPage(Object page) { - for (Object listener : pageRequestListeners) { - if (applicationContext.isDebug()) { - try { - ReflectionUtil.invoke(listener.getClass(), listener, "beforeProcessPage", page); - } catch (ReflectionException e) { - throw new NocturneException("Can't invoke handleBeforeProcessPage.", e); - } - } else { - ((PageRequestListener) listener).beforeProcessPage((Page) page); - } - } - } - - private void handleAfterProcessPage(Object page, Throwable t) { - for (Object listener : pageRequestListeners) { - if (applicationContext.isDebug()) { - try { - ReflectionUtil.invoke(listener.getClass(), listener, "afterProcessPage", page, t); - } catch (ReflectionException e) { - throw new NocturneException("Can't invoke afterProcessPage.", e); - } - } else { - ((PageRequestListener) listener).afterProcessPage((Page) page, t); - } - } - } - - /** - * @return filterConfig Returns filter configuration instance. - */ - private FilterConfig getFilterConfig() { - return filterConfig; - } - - /** - * Run debug mode request handling. - * - * @param request Request. - * @param response Response. - * @return RunResult page run result. - */ - private RunResult runDebugService(HttpServletRequest request, HttpServletResponse response) { - applicationContext.setReloadingClassLoader(reloadingClassLoader); - RunResult runResult = new RunResult(); - - Object page = null; - Throwable pageThrowable = null; - - try { - Class pageLoaderClass = reloadingClassLoader.loadClass(PageLoader.class.getName()); - @SuppressWarnings("unchecked") Object pageLoader = pageLoaderClass.getConstructor().newInstance(); - - page = ReflectionUtil.invoke(pageLoader, "loadPage", request.getServletPath(), - RequestUtil.getRequestParams(request)); - if (page == null) { - runResult.setProcessChain(true); - } else { - processPage(request, response, page, runResult); - } - } catch (Throwable e) { - pageThrowable = e; - System.err.println("Can't process " + request.getRequestURL() + '.'); - e.printStackTrace(System.err); - logger.fatal("Can't process " + request.getRequestURL() + '.', e); - } finally { - if (page != null) { - handleAfterProcessPage(page, pageThrowable); - } - } - - //applicationContext.setInjector(null); - return runResult; - } - - /** - * Setups page fields and calls specific methods. - * - * @param request Request. - * @param response Response. - * @param page Page instance. - * @param runResult Run result to be modified during processing. - * @throws IOException when fails IO. - * @throws ClassNotFoundException If requested classes not found. - */ - private void processPage(HttpServletRequest request, HttpServletResponse response, Object page, RunResult runResult) throws IOException, ClassNotFoundException { - Configuration templateEngineConfiguration = templateEngineConfigurationPool.getInstance(); - - try { - ReflectionUtil.invoke(page, "setTemplateEngineConfiguration", templateEngineConfiguration); - ReflectionUtil.invoke(page, "setRequest", request); - ReflectionUtil.invoke(page, "setResponse", response); - ReflectionUtil.invoke(page, "setFilterConfig", filterConfig); - - setupPageRequestListener(page); - handleBeforeProcessPage(page); - ReflectionUtil.invoke(page, "parseTemplate"); - runResult.setProcessChain((Boolean) ReflectionUtil.invoke(page, "isProcessChain")); - - ((Flushable) ReflectionUtil.invoke(page, "getOutputStream")).flush(); - ((Flushable) ReflectionUtil.invoke(page, "getWriter")).flush(); - } catch (ReflectionException e) { - throw new NocturneException("Can't run method via reflection.", e); - } finally { - templateEngineConfigurationPool.release(templateEngineConfiguration); - } - } - - /** - * Initializes nocturne application. - * Reads configuration parameters from web.xml. - * - * @param config Config. - * @throws ServletException when method fails. - */ - public void init(FilterConfig config) throws ServletException { - try { - templateEngineConfigurationPool = new TemplateEngineConfigurationPool(config); - - filterConfig = config; - applicationContext.setServletContext(config.getServletContext()); - - // Pass application context to servlet - config.getServletContext().setAttribute("applicationContext", applicationContext); - - ApplicationContextLoader.initialize(); - - // Log. - if (!applicationContext.isDebug()) { - logger.info("Nocturne RequestDispatcher has been initialized."); - } - } catch (Exception e) { - logger.error("Exception while initialization DispatchFilter.", e); - throw new ServletException(e); - } - } - - /** - * Handles requests to the application pages. - * - * @param request Request. - * @param response Response. - * @param filterChain Filter chain. - * @throws ServletException when method fails. - * @throws IOException when something wrong with IO. - */ - public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { - try { - //applicationContext.clearComponentsByTemplate(); - applicationContext.setRequestAndResponse(request, response); - - setupHeaders(response); - RunResult runResult; - - if (applicationContext.isDebug()) { - runResult = runDebugService(request, response); - } else { - runResult = runProductionService(request, response); - } - //applicationContext.clearComponentsByTemplate(); - - if (runResult.isProcessChain()) { - filterChain.doFilter(request, response); - } - } catch (Exception e) { - logger.error("Exception while processing request.", e); - throw new ServletException(e); - } finally { - applicationContext.unsetRequestAndResponse(); - } - } - - private static void setupHeaders(HttpServletResponse response) { - response.setHeader("Cache-Control", "private,no-cache,no-store,max-age=0,must-revalidate"); - response.setHeader("Expires", "-1"); - response.setHeader("Pragma", "no-cache"); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - response.setContentType("text/html"); - } - - /** - * Destroy filter. - */ - public void destroy() { - templateEngineConfigurationPool.close(); - pageLoader.close(); - - ApplicationContextLoader.shutdown(); - } - - private static class RunResult { - private boolean processChain; - - public boolean isProcessChain() { - return processChain; - } - - public void setProcessChain(boolean processChain) { - this.processChain = processChain; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import freemarker.template.Configuration; +import org.apache.log4j.Logger; +import org.nocturne.exception.NocturneException; +import org.nocturne.exception.ReflectionException; +import org.nocturne.listener.PageRequestListener; +import org.nocturne.pool.TemplateEngineConfigurationPool; +import org.nocturne.util.ReflectionUtil; +import org.nocturne.util.RequestUtil; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.Flushable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Mike Mirzayanov + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class RequestDispatcher { + private static final Logger logger = Logger.getLogger(RequestDispatcher.class); + + private final ApplicationContext applicationContext = ApplicationContext.getInstance(); + + /** + * Freemarker configuration. + */ + private TemplateEngineConfigurationPool templateEngineConfigurationPool; + + /** + * Page loader for production mode. + */ + private final PageLoader pageLoader = new PageLoader(); + + /** + * Servlet config. + */ + private FilterConfig filterConfig; + + /** + * Listens requests for pages. + */ + private List pageRequestListeners; + + /** + * Class loader used when application has been accessed in the debug mode. + */ + private ClassLoader reloadingClassLoader; + + void setReloadingClassLoader(ClassLoader reloadingClassLoader) { + this.reloadingClassLoader = reloadingClassLoader; + } + + private static boolean isClientAbortException(Exception e) { + return e != null && (e.getClass().getName().contains("ClientAbortException") + || (e.getCause() != null && e.getCause().getClass().getName().contains("ClientAbortException"))); + } + + /** + * Run production mode request handling. + * + * @param request Request. + * @param response Response. + * @return Page run result. + * @throws IOException when Something wrong with IO. + */ + private RunResult runProductionService(HttpServletRequest request, HttpServletResponse response) throws IOException { + RunResult result = new RunResult(); + + String path = request.getServletPath(); + + Map> parameterMap = RequestUtil.getRequestParams(request); + Page page = pageLoader.loadPage(path, parameterMap); + + if (page == null) { + result.setProcessChain(true); + return result; + } + + Configuration templateEngineConfiguration = templateEngineConfigurationPool.getInstance(); + + boolean processChain = false; + Throwable pageThrowable = null; + + try { + page.setTemplateEngineConfiguration(templateEngineConfiguration); + page.setRequest(request); + page.setFilterConfig(filterConfig); + page.setResponse(response); + + setupPageRequestListener(page); + handleBeforeProcessPage(page); + + page.parseTemplate(); + processChain = page.isProcessChain(); + + page.getOutputStream().flush(); + page.getWriter().flush(); + } catch (Exception e) { + pageThrowable = e; + if (!isClientAbortException(e)) { + e.printStackTrace(System.err); + System.err.flush(); + e.printStackTrace(System.out); + System.out.flush(); + logger.fatal("Can't process " + request.getRequestURL() + '.', e); + } + } finally { + handleAfterProcessPage(page, pageThrowable); + pageLoader.unloadPage(path, parameterMap, page); + templateEngineConfigurationPool.release(templateEngineConfiguration); + } + + result.setProcessChain(processChain); + return result; + } + + private void setupPageRequestListener(Object page) throws ClassNotFoundException { + if (pageRequestListeners == null || applicationContext.isDebug()) { + pageRequestListeners = new CopyOnWriteArrayList<>(); + + for (String name : applicationContext.getPageRequestListeners()) { + ClassLoader loader = page.getClass().getClassLoader(); + Class clazz = loader.loadClass(name); + Object listener = applicationContext.getInjector().getInstance(clazz); + pageRequestListeners.add(listener); + } + } + } + + private void handleBeforeProcessPage(Object page) { + for (Object listener : pageRequestListeners) { + if (applicationContext.isDebug()) { + try { + ReflectionUtil.invoke(listener.getClass(), listener, "beforeProcessPage", page); + } catch (ReflectionException e) { + throw new NocturneException("Can't invoke handleBeforeProcessPage.", e); + } + } else { + ((PageRequestListener) listener).beforeProcessPage((Page) page); + } + } + } + + private void handleAfterProcessPage(Object page, Throwable t) { + for (Object listener : pageRequestListeners) { + if (applicationContext.isDebug()) { + try { + ReflectionUtil.invoke(listener.getClass(), listener, "afterProcessPage", page, t); + } catch (ReflectionException e) { + throw new NocturneException("Can't invoke afterProcessPage.", e); + } + } else { + ((PageRequestListener) listener).afterProcessPage((Page) page, t); + } + } + } + + /** + * @return filterConfig Returns filter configuration instance. + */ + private FilterConfig getFilterConfig() { + return filterConfig; + } + + /** + * Run debug mode request handling. + * + * @param request Request. + * @param response Response. + * @return RunResult page run result. + */ + private RunResult runDebugService(HttpServletRequest request, HttpServletResponse response) { + applicationContext.setReloadingClassLoader(reloadingClassLoader); + RunResult runResult = new RunResult(); + + Object page = null; + Throwable pageThrowable = null; + + try { + Class pageLoaderClass = reloadingClassLoader.loadClass(PageLoader.class.getName()); + @SuppressWarnings("unchecked") Object pageLoader = pageLoaderClass.getConstructor().newInstance(); + + page = ReflectionUtil.invoke(pageLoader, "loadPage", request.getServletPath(), + RequestUtil.getRequestParams(request)); + if (page == null) { + runResult.setProcessChain(true); + } else { + processPage(request, response, page, runResult); + } + } catch (Throwable e) { + pageThrowable = e; + System.err.println("Can't process " + request.getRequestURL() + '.'); + e.printStackTrace(System.err); + logger.fatal("Can't process " + request.getRequestURL() + '.', e); + } finally { + if (page != null) { + handleAfterProcessPage(page, pageThrowable); + } + } + + //applicationContext.setInjector(null); + return runResult; + } + + /** + * Setups page fields and calls specific methods. + * + * @param request Request. + * @param response Response. + * @param page Page instance. + * @param runResult Run result to be modified during processing. + * @throws IOException when fails IO. + * @throws ClassNotFoundException If requested classes not found. + */ + private void processPage(HttpServletRequest request, HttpServletResponse response, Object page, RunResult runResult) throws IOException, ClassNotFoundException { + Configuration templateEngineConfiguration = templateEngineConfigurationPool.getInstance(); + + try { + ReflectionUtil.invoke(page, "setTemplateEngineConfiguration", templateEngineConfiguration); + ReflectionUtil.invoke(page, "setRequest", request); + ReflectionUtil.invoke(page, "setResponse", response); + ReflectionUtil.invoke(page, "setFilterConfig", filterConfig); + + setupPageRequestListener(page); + handleBeforeProcessPage(page); + ReflectionUtil.invoke(page, "parseTemplate"); + runResult.setProcessChain((Boolean) ReflectionUtil.invoke(page, "isProcessChain")); + + ((Flushable) ReflectionUtil.invoke(page, "getOutputStream")).flush(); + ((Flushable) ReflectionUtil.invoke(page, "getWriter")).flush(); + } catch (ReflectionException e) { + throw new NocturneException("Can't run method via reflection.", e); + } finally { + templateEngineConfigurationPool.release(templateEngineConfiguration); + } + } + + /** + * Initializes nocturne application. + * Reads configuration parameters from web.xml. + * + * @param config Config. + * @throws ServletException when method fails. + */ + public void init(FilterConfig config) throws ServletException { + try { + templateEngineConfigurationPool = new TemplateEngineConfigurationPool(config); + + filterConfig = config; + applicationContext.setServletContext(config.getServletContext()); + + // Pass application context to servlet + config.getServletContext().setAttribute("applicationContext", applicationContext); + + ApplicationContextLoader.initialize(); + + // Log. + if (!applicationContext.isDebug()) { + logger.info("Nocturne RequestDispatcher has been initialized."); + } + } catch (Exception e) { + logger.error("Exception while initialization DispatchFilter.", e); + throw new ServletException(e); + } + } + + /** + * Handles requests to the application pages. + * + * @param request Request. + * @param response Response. + * @param filterChain Filter chain. + * @throws ServletException when method fails. + * @throws IOException when something wrong with IO. + */ + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + try { + //applicationContext.clearComponentsByTemplate(); + applicationContext.setRequestAndResponse(request, response); + + setupHeaders(response); + RunResult runResult; + + if (applicationContext.isDebug()) { + runResult = runDebugService(request, response); + } else { + runResult = runProductionService(request, response); + } + //applicationContext.clearComponentsByTemplate(); + + if (runResult.isProcessChain()) { + filterChain.doFilter(request, response); + } + } catch (Exception e) { + logger.error("Exception while processing request.", e); + throw new ServletException(e); + } finally { + applicationContext.unsetRequestAndResponse(); + } + } + + private static void setupHeaders(HttpServletResponse response) { + response.setHeader("Cache-Control", "private,no-cache,no-store,max-age=0,must-revalidate"); + response.setHeader("Expires", "-1"); + response.setHeader("Pragma", "no-cache"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("text/html"); + } + + /** + * Destroy filter. + */ + public void destroy() { + templateEngineConfigurationPool.close(); + pageLoader.close(); + + ApplicationContextLoader.shutdown(); + } + + private static class RunResult { + private boolean processChain; + + public boolean isProcessChain() { + return processChain; + } + + public void setProcessChain(boolean processChain) { + this.processChain = processChain; + } + } +} diff --git a/code/src/main/java/org/nocturne/main/RequestRouter.java b/code/src/main/java/org/nocturne/main/RequestRouter.java index 7e3bd1d..c7312a1 100644 --- a/code/src/main/java/org/nocturne/main/RequestRouter.java +++ b/code/src/main/java/org/nocturne/main/RequestRouter.java @@ -1,99 +1,99 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.main; - -import org.nocturne.collection.SingleEntryList; - -import java.util.*; - -/** - * @author Mike Mirzayanov - */ -@SuppressWarnings("InnerClassOfInterface") -public interface RequestRouter { - /** - * Override this method to return Resolution instance. - * To dispatch URLs like "/PageClass": {@code - *
-     *     return new Resolution("your.application.pages." + path.substring(1));
-     * 
- * } - *

- * Should be thread-safe. - *

- * - * @param path Page path, for example "/login"; - * @param parameterMap Contains parameters (from regquest.getParametersMap()). - * @return Resolution, containing page class and action name. Also it can add own parameters. - */ - Resolution route(String path, Map> parameterMap); - - /** - * Incapsulates response from RequestRouter: the controller class, - * action and override parameters. - */ - @SuppressWarnings({"WeakerAccess", "unused"}) - class Resolution { - /** - * Controller class name. - */ - private final String pageClassName; - - /** - * Action name. - */ - private final String action; - - /** - * Parameters which will be also injected for @Parameter annotation. - */ - private final Map> overrideParameters = new HashMap<>(); - - /** - * @param pageClassName Controller class name. - * @param action Action name. - */ - public Resolution(String pageClassName, String action) { - this.pageClassName = pageClassName; - this.action = action == null ? "" : action; - } - - /** - * @param key Parameter name. - * @param value Parameter value. - */ - public void addOverrideParameter(String key, String value) { - overrideParameters.put(key, new SingleEntryList<>(value)); - } - - /** - * @param key Parameter name. - * @param values Parameter values. - */ - public void addOverrideParameter(String key, List values) { - overrideParameters.put(key, new ArrayList<>(values)); - } - - /** - * @return Controller class name. - */ - public String getPageClassName() { - return pageClassName; - } - - /** - * @return Action name or empty string if not specified. - */ - public String getAction() { - return action; - } - - /** - * @return Parameters which will be also injected for @Parameter annotation. - */ - public Map> getOverrideParameters() { - return Collections.unmodifiableMap(overrideParameters); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.main; + +import org.nocturne.collection.SingleEntryList; + +import java.util.*; + +/** + * @author Mike Mirzayanov + */ +@SuppressWarnings("InnerClassOfInterface") +public interface RequestRouter { + /** + * Override this method to return Resolution instance. + * To dispatch URLs like "/PageClass": {@code + *
+     *     return new Resolution("your.application.pages." + path.substring(1));
+     * 
+ * } + *

+ * Should be thread-safe. + *

+ * + * @param path Page path, for example "/login"; + * @param parameterMap Contains parameters (from regquest.getParametersMap()). + * @return Resolution, containing page class and action name. Also it can add own parameters. + */ + Resolution route(String path, Map> parameterMap); + + /** + * Incapsulates response from RequestRouter: the controller class, + * action and override parameters. + */ + @SuppressWarnings({"WeakerAccess", "unused"}) + class Resolution { + /** + * Controller class name. + */ + private final String pageClassName; + + /** + * Action name. + */ + private final String action; + + /** + * Parameters which will be also injected for @Parameter annotation. + */ + private final Map> overrideParameters = new HashMap<>(); + + /** + * @param pageClassName Controller class name. + * @param action Action name. + */ + public Resolution(String pageClassName, String action) { + this.pageClassName = pageClassName; + this.action = action == null ? "" : action; + } + + /** + * @param key Parameter name. + * @param value Parameter value. + */ + public void addOverrideParameter(String key, String value) { + overrideParameters.put(key, new SingleEntryList<>(value)); + } + + /** + * @param key Parameter name. + * @param values Parameter values. + */ + public void addOverrideParameter(String key, List values) { + overrideParameters.put(key, new ArrayList<>(values)); + } + + /** + * @return Controller class name. + */ + public String getPageClassName() { + return pageClassName; + } + + /** + * @return Action name or empty string if not specified. + */ + public String getAction() { + return action; + } + + /** + * @return Parameters which will be also injected for @Parameter annotation. + */ + public Map> getOverrideParameters() { + return Collections.unmodifiableMap(overrideParameters); + } + } +} diff --git a/code/src/main/java/org/nocturne/module/Configuration.java b/code/src/main/java/org/nocturne/module/Configuration.java index 93695cd..e3cd395 100644 --- a/code/src/main/java/org/nocturne/module/Configuration.java +++ b/code/src/main/java/org/nocturne/module/Configuration.java @@ -1,37 +1,37 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.module; - -import com.google.inject.Binder; - -/** - * Interface to be implemented in modules to setup them. - * - * @author Mike Mirzayanov - * @author Maxim Shipko (sladethe@gmail.com) - */ -public interface Configuration { - /** - * This class should contain pages registration. - * For example: {@code Links.add(UserPage.class);} - */ - void addPages(); - - /** - * Should contain configuration of the module IoC. - * Example: {@code binder.bind(BlogDao.class).to(BlogDaoImpl.class);} - * - * @param binder Guice binder. - */ - void bind(Binder binder); - - /** - * Sends a shutdown signal to the module and exits. May perform synchronously some finalization routines, - * but there is no guarantee that the module is completely stopped when {@code shutdown} method finishes. - *

- * The method should not fail in case of repeated/concurrent calls. - */ - default void shutdown() { - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.module; + +import com.google.inject.Binder; + +/** + * Interface to be implemented in modules to setup them. + * + * @author Mike Mirzayanov + * @author Maxim Shipko (sladethe@gmail.com) + */ +public interface Configuration { + /** + * This class should contain pages registration. + * For example: {@code Links.add(UserPage.class);} + */ + void addPages(); + + /** + * Should contain configuration of the module IoC. + * Example: {@code binder.bind(BlogDao.class).to(BlogDaoImpl.class);} + * + * @param binder Guice binder. + */ + void bind(Binder binder); + + /** + * Sends a shutdown signal to the module and exits. May perform synchronously some finalization routines, + * but there is no guarantee that the module is completely stopped when {@code shutdown} method finishes. + *

+ * The method should not fail in case of repeated/concurrent calls. + */ + default void shutdown() { + } +} diff --git a/code/src/main/java/org/nocturne/module/FileResourceLoader.java b/code/src/main/java/org/nocturne/module/FileResourceLoader.java index cbead78..4a7760a 100644 --- a/code/src/main/java/org/nocturne/module/FileResourceLoader.java +++ b/code/src/main/java/org/nocturne/module/FileResourceLoader.java @@ -1,28 +1,28 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.module; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -public class FileResourceLoader implements ResourceLoader { - private final File baseDir; - - public FileResourceLoader(File baseDir) { - this.baseDir = baseDir; - } - - @Override - public InputStream getResourceInputStream(String path) throws IOException { - File file = new File(baseDir, path); - - if (file.isFile()) { - return new FileInputStream(file); - } else { - return null; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.module; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileResourceLoader implements ResourceLoader { + private final File baseDir; + + public FileResourceLoader(File baseDir) { + this.baseDir = baseDir; + } + + @Override + public InputStream getResourceInputStream(String path) throws IOException { + File file = new File(baseDir, path); + + if (file.isFile()) { + return new FileInputStream(file); + } else { + return null; + } + } +} diff --git a/code/src/main/java/org/nocturne/module/Module.java b/code/src/main/java/org/nocturne/module/Module.java index 4190fa0..136393d 100644 --- a/code/src/main/java/org/nocturne/module/Module.java +++ b/code/src/main/java/org/nocturne/module/Module.java @@ -1,398 +1,398 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.module; - -import freemarker.cache.TemplateLoader; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.nocturne.exception.ModuleInitializationException; -import org.nocturne.main.ApplicationContext; -import org.nocturne.util.FileUtil; -import org.nocturne.util.StreamUtil; - -import javax.servlet.ServletContext; -import java.io.*; -import java.net.URL; -import java.util.Enumeration; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.regex.Pattern; - -/** - * Stores information about the module. - * - * @author Mike Mirzayanov. - */ -public class Module { - private static final Pattern MODULE_URL_MATCH_PATTERN = Pattern.compile("module-.*\\.jar"); - - /** - * Module name. - */ - private String name; - - /** - * Module jar file in WEB-INF/lib (or in other directory of classpath) - */ - private final JarFile file; - - /** - * Directories for debug. - */ - private final DebugContext debugContext = new DebugContext(); - - /** - * Module priority. Modules with highter priority will be loaded later and can override previously loaded modules setup. - */ - private int priority; - - /** - * Module freemarker template loader. - */ - private TemplateLoader templateLoader; - - /** - * Module resource loader. - */ - private ResourceLoader resourceLoader; - - /** - * Specific module configuration. Each module contains its own implementation of this interface. - */ - private Configuration configuration; - - /** - * Each module can specify implementation of Runnable to be loaded on module startup (once). - */ - private String startupClassName; - - private static ApplicationContext getApplicationContext() { - return ApplicationContext.getInstance(); - } - - /** - * @param url URL containing path to module JAR-file. - */ - public Module(URL url) { - try { - file = new JarFile(FileUtils.toFile(url)); - } catch (IOException e) { - throw new ModuleInitializationException("Can't create JarFile instance from " + url + '.', e); - } - } - - /** - * @return Each module can specify implementation of Runnable to be loaded on - */ - public String getStartupClassName() { - return startupClassName; - } - - /** - * @return Specific module configuration. Each module contains its own implementation of this interface. - */ - public Configuration getConfiguration() { - return configuration; - } - - /** - * @return Module name. - */ - public String getName() { - return name; - } - - /** - * @return Module freemarker template loader. - */ - public TemplateLoader getTemplateLoader() { - return templateLoader; - } - - /** - * @return Module resource loader. - */ - public ResourceLoader getResourceLoader() { - return resourceLoader; - } - - /** - * @return Module priority. Modules with higher priority will be loaded later and can override previously loaded modules setup. - */ - public int getPriority() { - return priority; - } - - /** - * @return Module jar file in WEB-INF/lib (or in other directory of classpath). - */ - public JarFile getFile() { - return file; - } - - /** - * @return Directories for debug. - */ - public DebugContext getDebugContext() { - return debugContext; - } - - /** - * Internal nocturne method to read module properties and construct Module instance completely. - */ - public void init() { - JarEntry webappEntry = file.getJarEntry("module.xml"); - - if (webappEntry.isDirectory()) { - throw new ModuleInitializationException("Entry module.xml should be file in module " + file.getName() + '.'); - } - - try (InputStream inputStream = file.getInputStream(webappEntry)) { - byte[] moduleXmlBytes = StreamUtil.getAsByteArray(inputStream); - - if (getApplicationContext().isDebug()) { - initializeForDebug(moduleXmlBytes); - - setupTemplateLoader(); - setupResourceLoader(); - - getApplicationContext().addReloadingClassPath(new File(debugContext.getClassesDir())); - } else { - initializeForProduction(moduleXmlBytes); - } - - setupPriority(moduleXmlBytes); - setupConfigurationClassName(moduleXmlBytes); - setupStartupClassName(moduleXmlBytes); - setupName(moduleXmlBytes); - } catch (IOException e) { - throw new ModuleInitializationException("Can't perform IO operation [module=" + file.getName() + "].", e); - } - } - - private void setupStartupClassName(byte[] moduleXmlBytes) { - try { - startupClassName = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/properties/startup-class", - String.class - ); - } catch (Exception ignored) { - // Optional parameter. - } - } - - private void setupConfigurationClassName(byte[] moduleXmlBytes) { - try { - String configurationClassName = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/properties/configuration-class", - String.class - ); - - configuration = (Configuration) getClass().getClassLoader().loadClass(configurationClassName).getConstructor().newInstance(); - } catch (Exception e) { - throw new ModuleInitializationException("Can't find element /module/properties/configuration-class " + - "or it contains illegal value.", e); - } - } - - private void setupName(byte[] moduleXmlBytes) { - name = FileUtil.extractFromXml(new ByteArrayInputStream(moduleXmlBytes), "/module/name", String.class); - } - - private void setupResourceLoader() { - resourceLoader = new FileResourceLoader(new File(debugContext.getWebappDir())); - } - - private void setupTemplateLoader() { - try { - templateLoader = new PreprocessFreemarkerFileTemplateLoader( - new File(debugContext.getTemplateDir()) - ); - } catch (IOException e) { - throw new ModuleInitializationException("Can't create module template loader.", e); - } - } - - private void setupPriority(byte[] moduleXmlBytes) { - try { - String priority = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/properties/priority", - String.class - ).trim(); - - if (priority.isEmpty()) { - this.priority = 1; - } else { - this.priority = Integer.valueOf(priority); - } - } catch (Exception e) { - throw new ModuleInitializationException("Can't find or parse /module/properties/priority. " + - "It expected to be an integer.", e); - } - } - - @SuppressWarnings({"unchecked", "AccessOfSystemProperties", "OverlyStrongTypeCast", "UseOfPropertiesAsHashtable"}) - private void initializeForDebug(byte[] moduleXmlBytes) { - String webappDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/debug/directories/webapp", - String.class - ); - - String templatesDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/debug/directories/templates", - String.class - ); - - String classesDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), - "/module/debug/directories/classes", - String.class - ); - - if ("true".equalsIgnoreCase(System.getProperty("dreamcatcher.loaded"))) { - ((Set) System.getProperties().get("dreamcatcher.listen-directories")).add(classesDir); - } - - debugContext.setWebappDir(webappDir); - debugContext.setTemplateDir(templatesDir); - debugContext.setClassesDir(classesDir); - } - - private void initializeForProduction(byte[] moduleXmlBytes) throws IOException { - String webappDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), "/module/directories/webapp", String.class - ); - - String templatesDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), "/module/directories/templates", String.class - ); - - String webInfDir = FileUtil.extractFromXml( - new ByteArrayInputStream(moduleXmlBytes), "/module/directories/WEB-INF", String.class - ); - - ServletContext servletContext = getApplicationContext().getServletContext(); - String[] templatePaths = getApplicationContext().getTemplatePaths(); - - copyFiles(servletContext, new File(webappDir), new File(".")); - copyFiles(servletContext, new File(templatesDir), new File(templatePaths[templatePaths.length - 1])); - copyFiles(servletContext, new File(webInfDir), new File("WEB-INF")); - } - - private void copyFiles(ServletContext servletContext, File sourceDir, - File targetDir) throws IOException { - Enumeration entries = file.entries(); - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - File entryPathFile = new File(entry.getName()); - boolean matched = false; - while (entryPathFile != null) { - if (sourceDir.equals(entryPathFile)) { - matched = true; - break; - } - entryPathFile = entryPathFile.getParentFile(); - } - - if (!matched) { - continue; - } - - if (entry.isDirectory()) { - File relativeFile = new File(targetDir, cutDir(sourceDir, new File(entry.getName())).getPath()); - - String realPath = FileUtil.getRealPath(servletContext, relativeFile.getPath()); - if (realPath == null) { - throw new ModuleInitializationException("Path '" + relativeFile.getPath() - + "' expected to be a directory in servletContext."); - } - - File entryDir = new File(realPath); - - if (!entryDir.isDirectory()) { - if (entryDir.isFile()) { - throw new ModuleInitializationException("Path " + entryDir + " expected " + - "to be a directory by " + file.getName() + '.'); - } else { - if (!entryDir.mkdirs()) { - throw new ModuleInitializationException("Can't create " + entryDir + " for " + - "module " + file.getName() + '.'); - } - } - } - } else { - File relativeFile = new File(targetDir, cutDir(sourceDir, new File(entry.getName())).getPath()); - - String realPath = FileUtil.getRealPath(servletContext, relativeFile.getPath()); - if (realPath == null) { - throw new ModuleInitializationException("Path '" + relativeFile.getPath() + "' expected to be a found in servletContext."); - } - - File entryFile = new File(realPath); - - //noinspection ResultOfMethodCallIgnored - entryFile.getParentFile().mkdirs(); - StreamUtil.copyInputStream( - file.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(entryFile)) - ); - } - } - } - - private static File cutDir(File dir, File file) { - return new File(file.getPath().substring(dir.getPath().length())); - } - - /** - * Checks if given URL contains JAR-file with nocturne module. - * - * @param url Url to check. - * @return {@code true} iff given URL contains JAR-file with nocturne module. - */ - public static boolean isModuleUrl(URL url) { - File file = new File(url.getFile()); - return MODULE_URL_MATCH_PATTERN.matcher(file.getName()).matches(); - } - - /** - * Directories where to find files of the modules which can be reloaded in the development. - */ - static class DebugContext { - private String webappDir; - private String templateDir; - private String classesDir; - - private void setWebappDir(String webappDir) { - this.webappDir = webappDir; - } - - private void setTemplateDir(String templateDir) { - this.templateDir = templateDir; - } - - public String getWebappDir() { - return webappDir; - } - - public String getTemplateDir() { - return templateDir; - } - - public String getClassesDir() { - return classesDir; - } - - public void setClassesDir(String classesDir) { - this.classesDir = classesDir; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.module; + +import freemarker.cache.TemplateLoader; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.nocturne.exception.ModuleInitializationException; +import org.nocturne.main.ApplicationContext; +import org.nocturne.util.FileUtil; +import org.nocturne.util.StreamUtil; + +import javax.servlet.ServletContext; +import java.io.*; +import java.net.URL; +import java.util.Enumeration; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +/** + * Stores information about the module. + * + * @author Mike Mirzayanov. + */ +public class Module { + private static final Pattern MODULE_URL_MATCH_PATTERN = Pattern.compile("module-.*\\.jar"); + + /** + * Module name. + */ + private String name; + + /** + * Module jar file in WEB-INF/lib (or in other directory of classpath) + */ + private final JarFile file; + + /** + * Directories for debug. + */ + private final DebugContext debugContext = new DebugContext(); + + /** + * Module priority. Modules with highter priority will be loaded later and can override previously loaded modules setup. + */ + private int priority; + + /** + * Module freemarker template loader. + */ + private TemplateLoader templateLoader; + + /** + * Module resource loader. + */ + private ResourceLoader resourceLoader; + + /** + * Specific module configuration. Each module contains its own implementation of this interface. + */ + private Configuration configuration; + + /** + * Each module can specify implementation of Runnable to be loaded on module startup (once). + */ + private String startupClassName; + + private static ApplicationContext getApplicationContext() { + return ApplicationContext.getInstance(); + } + + /** + * @param url URL containing path to module JAR-file. + */ + public Module(URL url) { + try { + file = new JarFile(FileUtils.toFile(url)); + } catch (IOException e) { + throw new ModuleInitializationException("Can't create JarFile instance from " + url + '.', e); + } + } + + /** + * @return Each module can specify implementation of Runnable to be loaded on + */ + public String getStartupClassName() { + return startupClassName; + } + + /** + * @return Specific module configuration. Each module contains its own implementation of this interface. + */ + public Configuration getConfiguration() { + return configuration; + } + + /** + * @return Module name. + */ + public String getName() { + return name; + } + + /** + * @return Module freemarker template loader. + */ + public TemplateLoader getTemplateLoader() { + return templateLoader; + } + + /** + * @return Module resource loader. + */ + public ResourceLoader getResourceLoader() { + return resourceLoader; + } + + /** + * @return Module priority. Modules with higher priority will be loaded later and can override previously loaded modules setup. + */ + public int getPriority() { + return priority; + } + + /** + * @return Module jar file in WEB-INF/lib (or in other directory of classpath). + */ + public JarFile getFile() { + return file; + } + + /** + * @return Directories for debug. + */ + public DebugContext getDebugContext() { + return debugContext; + } + + /** + * Internal nocturne method to read module properties and construct Module instance completely. + */ + public void init() { + JarEntry webappEntry = file.getJarEntry("module.xml"); + + if (webappEntry.isDirectory()) { + throw new ModuleInitializationException("Entry module.xml should be file in module " + file.getName() + '.'); + } + + try (InputStream inputStream = file.getInputStream(webappEntry)) { + byte[] moduleXmlBytes = StreamUtil.getAsByteArray(inputStream); + + if (getApplicationContext().isDebug()) { + initializeForDebug(moduleXmlBytes); + + setupTemplateLoader(); + setupResourceLoader(); + + getApplicationContext().addReloadingClassPath(new File(debugContext.getClassesDir())); + } else { + initializeForProduction(moduleXmlBytes); + } + + setupPriority(moduleXmlBytes); + setupConfigurationClassName(moduleXmlBytes); + setupStartupClassName(moduleXmlBytes); + setupName(moduleXmlBytes); + } catch (IOException e) { + throw new ModuleInitializationException("Can't perform IO operation [module=" + file.getName() + "].", e); + } + } + + private void setupStartupClassName(byte[] moduleXmlBytes) { + try { + startupClassName = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/properties/startup-class", + String.class + ); + } catch (Exception ignored) { + // Optional parameter. + } + } + + private void setupConfigurationClassName(byte[] moduleXmlBytes) { + try { + String configurationClassName = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/properties/configuration-class", + String.class + ); + + configuration = (Configuration) getClass().getClassLoader().loadClass(configurationClassName).getConstructor().newInstance(); + } catch (Exception e) { + throw new ModuleInitializationException("Can't find element /module/properties/configuration-class " + + "or it contains illegal value.", e); + } + } + + private void setupName(byte[] moduleXmlBytes) { + name = FileUtil.extractFromXml(new ByteArrayInputStream(moduleXmlBytes), "/module/name", String.class); + } + + private void setupResourceLoader() { + resourceLoader = new FileResourceLoader(new File(debugContext.getWebappDir())); + } + + private void setupTemplateLoader() { + try { + templateLoader = new PreprocessFreemarkerFileTemplateLoader( + new File(debugContext.getTemplateDir()) + ); + } catch (IOException e) { + throw new ModuleInitializationException("Can't create module template loader.", e); + } + } + + private void setupPriority(byte[] moduleXmlBytes) { + try { + String priority = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/properties/priority", + String.class + ).trim(); + + if (priority.isEmpty()) { + this.priority = 1; + } else { + this.priority = Integer.valueOf(priority); + } + } catch (Exception e) { + throw new ModuleInitializationException("Can't find or parse /module/properties/priority. " + + "It expected to be an integer.", e); + } + } + + @SuppressWarnings({"unchecked", "AccessOfSystemProperties", "OverlyStrongTypeCast", "UseOfPropertiesAsHashtable"}) + private void initializeForDebug(byte[] moduleXmlBytes) { + String webappDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/debug/directories/webapp", + String.class + ); + + String templatesDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/debug/directories/templates", + String.class + ); + + String classesDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), + "/module/debug/directories/classes", + String.class + ); + + if ("true".equalsIgnoreCase(System.getProperty("dreamcatcher.loaded"))) { + ((Set) System.getProperties().get("dreamcatcher.listen-directories")).add(classesDir); + } + + debugContext.setWebappDir(webappDir); + debugContext.setTemplateDir(templatesDir); + debugContext.setClassesDir(classesDir); + } + + private void initializeForProduction(byte[] moduleXmlBytes) throws IOException { + String webappDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), "/module/directories/webapp", String.class + ); + + String templatesDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), "/module/directories/templates", String.class + ); + + String webInfDir = FileUtil.extractFromXml( + new ByteArrayInputStream(moduleXmlBytes), "/module/directories/WEB-INF", String.class + ); + + ServletContext servletContext = getApplicationContext().getServletContext(); + String[] templatePaths = getApplicationContext().getTemplatePaths(); + + copyFiles(servletContext, new File(webappDir), new File(".")); + copyFiles(servletContext, new File(templatesDir), new File(templatePaths[templatePaths.length - 1])); + copyFiles(servletContext, new File(webInfDir), new File("WEB-INF")); + } + + private void copyFiles(ServletContext servletContext, File sourceDir, + File targetDir) throws IOException { + Enumeration entries = file.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + File entryPathFile = new File(entry.getName()); + boolean matched = false; + while (entryPathFile != null) { + if (sourceDir.equals(entryPathFile)) { + matched = true; + break; + } + entryPathFile = entryPathFile.getParentFile(); + } + + if (!matched) { + continue; + } + + if (entry.isDirectory()) { + File relativeFile = new File(targetDir, cutDir(sourceDir, new File(entry.getName())).getPath()); + + String realPath = FileUtil.getRealPath(servletContext, relativeFile.getPath()); + if (realPath == null) { + throw new ModuleInitializationException("Path '" + relativeFile.getPath() + + "' expected to be a directory in servletContext."); + } + + File entryDir = new File(realPath); + + if (!entryDir.isDirectory()) { + if (entryDir.isFile()) { + throw new ModuleInitializationException("Path " + entryDir + " expected " + + "to be a directory by " + file.getName() + '.'); + } else { + if (!entryDir.mkdirs()) { + throw new ModuleInitializationException("Can't create " + entryDir + " for " + + "module " + file.getName() + '.'); + } + } + } + } else { + File relativeFile = new File(targetDir, cutDir(sourceDir, new File(entry.getName())).getPath()); + + String realPath = FileUtil.getRealPath(servletContext, relativeFile.getPath()); + if (realPath == null) { + throw new ModuleInitializationException("Path '" + relativeFile.getPath() + "' expected to be a found in servletContext."); + } + + File entryFile = new File(realPath); + + //noinspection ResultOfMethodCallIgnored + entryFile.getParentFile().mkdirs(); + StreamUtil.copyInputStream( + file.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(entryFile)) + ); + } + } + } + + private static File cutDir(File dir, File file) { + return new File(file.getPath().substring(dir.getPath().length())); + } + + /** + * Checks if given URL contains JAR-file with nocturne module. + * + * @param url Url to check. + * @return {@code true} iff given URL contains JAR-file with nocturne module. + */ + public static boolean isModuleUrl(URL url) { + File file = new File(url.getFile()); + return MODULE_URL_MATCH_PATTERN.matcher(file.getName()).matches(); + } + + /** + * Directories where to find files of the modules which can be reloaded in the development. + */ + static class DebugContext { + private String webappDir; + private String templateDir; + private String classesDir; + + private void setWebappDir(String webappDir) { + this.webappDir = webappDir; + } + + private void setTemplateDir(String templateDir) { + this.templateDir = templateDir; + } + + public String getWebappDir() { + return webappDir; + } + + public String getTemplateDir() { + return templateDir; + } + + public String getClassesDir() { + return classesDir; + } + + public void setClassesDir(String classesDir) { + this.classesDir = classesDir; + } + } +} diff --git a/code/src/main/java/org/nocturne/module/PreprocessFreemarkerFileTemplateLoader.java b/code/src/main/java/org/nocturne/module/PreprocessFreemarkerFileTemplateLoader.java index e76c01f..3f42eb7 100644 --- a/code/src/main/java/org/nocturne/module/PreprocessFreemarkerFileTemplateLoader.java +++ b/code/src/main/java/org/nocturne/module/PreprocessFreemarkerFileTemplateLoader.java @@ -1,249 +1,258 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.module; - -import freemarker.cache.FileTemplateLoader; -import freemarker.cache.MultiTemplateLoader; -import freemarker.cache.TemplateLoader; -import org.apache.commons.io.IOUtils; -import org.nocturne.exception.ConfigurationException; -import org.nocturne.main.ApplicationContext; -import org.nocturne.main.ReloadingContext; -import org.nocturne.template.TemplatePreprocessor; -import org.nocturne.template.impl.ComponentTemplatePreprocessor; -import org.nocturne.util.StringUtil; - -import javax.security.auth.login.AppConfigurationEntry; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Scans loaded templates to contains {{...}} and uses captions framework to - * substitute them to caption values. Also prepares @once directive (sets scopes) and - * - * @author Mike Mirzayanov - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public class PreprocessFreemarkerFileTemplateLoader extends MultiTemplateLoader { - private static final org.apache.log4j.Logger logger - = org.apache.log4j.Logger.getLogger(PreprocessFreemarkerFileTemplateLoader.class); - - private static final ConcurrentMap templateSourceByName = new ConcurrentHashMap<>(); - private final int templateDirCount; - - public PreprocessFreemarkerFileTemplateLoader(File... templateDirs) throws IOException { - super(getTemplateLoaders(templateDirs)); - this.templateDirCount = templateDirs.length; - } - - private static TemplateLoader[] getTemplateLoaders(File[] templateDirs) throws IOException { - int templateDirCount = templateDirs.length; - if (templateDirCount <= 0) { - logger.error("Please specify at least one template directory."); - throw new ConfigurationException("Please specify at least one template directory."); - } - - TemplateLoader[] templateLoaders = new TemplateLoader[templateDirCount]; - - for (int dirIndex = 0; dirIndex < templateDirCount; ++dirIndex) { - templateLoaders[dirIndex] = new FileTemplateLoader(templateDirs[dirIndex]); - } - - return templateLoaders; - } - - @Override - public Object findTemplateSource(String name) throws IOException { - InmemoryTemplateSource templateSource = templateSourceByName.get(name); - - if (templateSource == null) { - if (templateDirCount > 1 && !ApplicationContext.getInstance().isStickyTemplatePaths()) { - resetState(); - } - - Object result = super.findTemplateSource(name); - if (result != null && !ReloadingContext.getInstance().isDebug()) { - try (Reader reader = super.getReader(result, StandardCharsets.UTF_8.name())) { - addTemplateSource(name, IOUtils.toString(reader)); - } - } - - return result; - } else { - return templateSource; - } - } - - @SuppressWarnings("RefusedBequest") - @Override - public Reader getReader(Object templateSource, String encoding) throws IOException { - StringBuilder stringBuilder = getTemplateAsStringBuilder(templateSource, encoding); - - if (ApplicationContext.getInstance().isUseComponentTemplates()) { - TemplatePreprocessor preprocessor = new ComponentTemplatePreprocessor(); - preprocessor.preprocess(templateSource, stringBuilder); - } - - processCaptions(stringBuilder); - processOnceDirectiveCalls(templateSource, stringBuilder); - return new StringReader(stringBuilder.toString()); - } - - private StringBuilder getTemplateAsStringBuilder(Object templateSource, String encoding) throws IOException { - if (templateSource instanceof InmemoryTemplateSource) { - return new StringBuilder(((InmemoryTemplateSource) templateSource).getContent()); - } else { - StringBuilder builder = new StringBuilder(); - try (Reader reader = super.getReader(templateSource, encoding)) { - char[] buffer = new char[65536]; - while (true) { - int readByteCount = reader.read(buffer); - if (readByteCount == -1) { - break; - } - builder.append(buffer, 0, readByteCount); - } - } - - return builder; - } - } - - /** - * Scans content to find "{{...some-text...}}" and replaces it using InteropImpl. - * - * @param sb content to be processed - */ - private static void processCaptions(StringBuilder sb) { - int index = 0; - - while (index + 1 < sb.length()) { - if (sb.charAt(index) == '{' && sb.charAt(index + 1) == '{') { - int closeIndex = sb.indexOf("}}", index); - - if (closeIndex >= 0) { - String content = sb.substring(index + 2, closeIndex); - - if (content.startsWith("!")) { - logger.error("{{!...}} syntax is no more supported."); - throw new UnsupportedOperationException("{{!...}} syntax is no more supported."); - } - - String replacement = ApplicationContext.getInstance().$(content); - sb.replace(index, closeIndex + 2, replacement); - } - } - - index++; - } - } - - private void processOnceDirectiveCalls(Object templateSource, StringBuilder sb) { - int index = 0; - while (index + 6 < sb.length()) { - if (sb.charAt(index) == '<' && sb.charAt(index + 1) == '@' && sb.charAt(index + 2) == 'o' - && sb.charAt(index + 3) == 'n' && sb.charAt(index + 4) == 'c' && sb.charAt(index + 5) == 'e' - && (Character.isWhitespace(sb.charAt(index + 6)) || sb.charAt(index + 6) == '>')) { - String scopeAttr = " scope=\"" + escape(templateSource.toString()) + ":" + index + "\""; - sb.insert(index + 6, scopeAttr); - } - index++; - } - } - - private String escape(String s) { - if (StringUtil.isEmpty(s)) { - return s; - } else { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) == '\\') { - result.append('/'); - continue; - } - if (s.charAt(i) == '\"') { - continue; - } - result.append(s.charAt(i)); - } - return result.toString(); - } - } - - /** - * Use it to override or setup template source by it's name. Parameter {@code content} will be used - * as a template source even if ftl-file exists. Current time will be used in cache routine - * as last modification time of template content. - * - * @param name Template name (for example, "IndexPage.ftl") - * @param content Template source - */ - public static void addTemplateSource(String name, String content) { - templateSourceByName.put(name, new InmemoryTemplateSource(name, content)); - } - - /** - * Use it to override or setup template source by it's name. Parameter {@code content} will be used - * as a template source even if ftl-file exists. Parameter {@code modificationTime} will be used in cache routine - * as last modification time of template content. - * - * @param name Template name (for example, "IndexPage.ftl") - * @param content Template source - * @param modificationTime Last modification time of template content - */ - public static void addTemplateSource(String name, String content, long modificationTime) { - templateSourceByName.put(name, new InmemoryTemplateSource(name, content, modificationTime)); - } - - @Override - public long getLastModified(Object templateSource) { - if (templateSource == null) { - return 0; - } - - if (templateSource instanceof InmemoryTemplateSource) { - return ((InmemoryTemplateSource) templateSource).lastModified(); - } - - return super.getLastModified(templateSource); - } - - @Override - public void closeTemplateSource(Object templateSource) throws IOException { - if (!(templateSource instanceof InmemoryTemplateSource)) { - super.closeTemplateSource(templateSource); - } - } - - @SuppressWarnings("DeserializableClassInSecureContext") - private static final class InmemoryTemplateSource extends File { - private final String content; - private final long modificationTime; - - private InmemoryTemplateSource(String name, String content) { - this(name, content, System.currentTimeMillis()); - } - - private InmemoryTemplateSource(String name, String content, long modificationTime) { - super(name); - this.content = content; - this.modificationTime = modificationTime; - } - - private String getContent() { - return content; - } - - @SuppressWarnings("RefusedBequest") - @Override - public long lastModified() { - return modificationTime; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.module; + +import freemarker.cache.FileTemplateLoader; +import freemarker.cache.MultiTemplateLoader; +import freemarker.cache.TemplateLoader; +import org.apache.commons.io.IOUtils; +import org.nocturne.exception.ConfigurationException; +import org.nocturne.main.ApplicationContext; +import org.nocturne.main.ReloadingContext; +import org.nocturne.template.TemplatePreprocessor; +import org.nocturne.template.impl.ComponentTemplatePreprocessor; +import org.nocturne.util.StringUtil; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Scans loaded templates to contains {{...}} and uses captions framework to + * substitute them to caption values. Also prepares @once directive (sets scopes) and + * + * @author Mike Mirzayanov + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class PreprocessFreemarkerFileTemplateLoader extends MultiTemplateLoader { + private static final org.apache.log4j.Logger logger + = org.apache.log4j.Logger.getLogger(PreprocessFreemarkerFileTemplateLoader.class); + + private static final ConcurrentMap templateSourceByName = new ConcurrentHashMap<>(); + private final int templateDirCount; + + public PreprocessFreemarkerFileTemplateLoader(File... templateDirs) throws IOException { + super(getTemplateLoaders(templateDirs)); + this.templateDirCount = templateDirs.length; + + StringBuilder sb = new StringBuilder(); + for (File templateDir : templateDirs) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append("\"").append(templateDir).append("\""); + } + + logger.info("PreprocessFreemarkerFileTemplateLoader has been constructed [templateDirs=" + sb + "]."); + } + + private static TemplateLoader[] getTemplateLoaders(File[] templateDirs) throws IOException { + int templateDirCount = templateDirs.length; + if (templateDirCount == 0) { + logger.error("Please specify at least one template directory."); + throw new ConfigurationException("Please specify at least one template directory."); + } + + TemplateLoader[] templateLoaders = new TemplateLoader[templateDirCount]; + + for (int dirIndex = 0; dirIndex < templateDirCount; ++dirIndex) { + templateLoaders[dirIndex] = new FileTemplateLoader(templateDirs[dirIndex]); + } + + return templateLoaders; + } + + @Override + public Object findTemplateSource(String name) throws IOException { + InmemoryTemplateSource templateSource = templateSourceByName.get(name); + + if (templateSource == null) { + if (templateDirCount > 1 && !ApplicationContext.getInstance().isStickyTemplatePaths()) { + resetState(); + } + + Object result = super.findTemplateSource(name); + if (result != null && !ReloadingContext.getInstance().isDebug()) { + try (Reader reader = super.getReader(result, StandardCharsets.UTF_8.name())) { + addTemplateSource(name, IOUtils.toString(reader)); + } + } + + return result; + } else { + return templateSource; + } + } + + @SuppressWarnings("RefusedBequest") + @Override + public Reader getReader(Object templateSource, String encoding) throws IOException { + StringBuilder stringBuilder = getTemplateAsStringBuilder(templateSource, encoding); + + if (ApplicationContext.getInstance().isUseComponentTemplates()) { + TemplatePreprocessor preprocessor = new ComponentTemplatePreprocessor(); + preprocessor.preprocess(templateSource, stringBuilder); + } + + processCaptions(stringBuilder); + processOnceDirectiveCalls(templateSource, stringBuilder); + return new StringReader(stringBuilder.toString()); + } + + private StringBuilder getTemplateAsStringBuilder(Object templateSource, String encoding) throws IOException { + if (templateSource instanceof InmemoryTemplateSource) { + return new StringBuilder(((InmemoryTemplateSource) templateSource).getContent()); + } else { + StringBuilder builder = new StringBuilder(); + try (Reader reader = super.getReader(templateSource, encoding)) { + char[] buffer = new char[65536]; + while (true) { + int readByteCount = reader.read(buffer); + if (readByteCount == -1) { + break; + } + builder.append(buffer, 0, readByteCount); + } + } + + return builder; + } + } + + /** + * Scans content to find "{{...some-text...}}" and replaces it using InteropImpl. + * + * @param sb content to be processed + */ + private static void processCaptions(StringBuilder sb) { + int index = 0; + + while (index + 1 < sb.length()) { + if (sb.charAt(index) == '{' && sb.charAt(index + 1) == '{') { + int closeIndex = sb.indexOf("}}", index); + + if (closeIndex >= 0) { + String content = sb.substring(index + 2, closeIndex); + + if (content.startsWith("!")) { + logger.error("{{!...}} syntax is no more supported."); + throw new UnsupportedOperationException("{{!...}} syntax is no more supported."); + } + + String replacement = ApplicationContext.getInstance().$(content); + sb.replace(index, closeIndex + 2, replacement); + } + } + + index++; + } + } + + private void processOnceDirectiveCalls(Object templateSource, StringBuilder sb) { + int index = 0; + while (index + 6 < sb.length()) { + if (sb.charAt(index) == '<' && sb.charAt(index + 1) == '@' && sb.charAt(index + 2) == 'o' + && sb.charAt(index + 3) == 'n' && sb.charAt(index + 4) == 'c' && sb.charAt(index + 5) == 'e' + && (Character.isWhitespace(sb.charAt(index + 6)) || sb.charAt(index + 6) == '>')) { + String scopeAttr = " scope=\"" + escape(templateSource.toString()) + ":" + index + "\""; + sb.insert(index + 6, scopeAttr); + } + index++; + } + } + + private String escape(String s) { + if (StringUtil.isEmpty(s)) { + return s; + } else { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '\\') { + result.append('/'); + continue; + } + if (s.charAt(i) == '\"') { + continue; + } + result.append(s.charAt(i)); + } + return result.toString(); + } + } + + /** + * Use it to override or setup template source by it's name. Parameter {@code content} will be used + * as a template source even if ftl-file exists. Current time will be used in cache routine + * as last modification time of template content. + * + * @param name Template name (for example, "IndexPage.ftl") + * @param content Template source + */ + public static void addTemplateSource(String name, String content) { + templateSourceByName.put(name, new InmemoryTemplateSource(name, content)); + } + + /** + * Use it to override or setup template source by it's name. Parameter {@code content} will be used + * as a template source even if ftl-file exists. Parameter {@code modificationTime} will be used in cache routine + * as last modification time of template content. + * + * @param name Template name (for example, "IndexPage.ftl") + * @param content Template source + * @param modificationTime Last modification time of template content + */ + public static void addTemplateSource(String name, String content, long modificationTime) { + templateSourceByName.put(name, new InmemoryTemplateSource(name, content, modificationTime)); + } + + @Override + public long getLastModified(Object templateSource) { + if (templateSource == null) { + return 0; + } + + if (templateSource instanceof InmemoryTemplateSource) { + return ((InmemoryTemplateSource) templateSource).lastModified(); + } + + return super.getLastModified(templateSource); + } + + @Override + public void closeTemplateSource(Object templateSource) throws IOException { + if (!(templateSource instanceof InmemoryTemplateSource)) { + super.closeTemplateSource(templateSource); + } + } + + @SuppressWarnings("DeserializableClassInSecureContext") + private static final class InmemoryTemplateSource extends File { + private final String content; + private final long modificationTime; + + private InmemoryTemplateSource(String name, String content) { + this(name, content, System.currentTimeMillis()); + } + + private InmemoryTemplateSource(String name, String content, long modificationTime) { + super(name); + this.content = content; + this.modificationTime = modificationTime; + } + + private String getContent() { + return content; + } + + @SuppressWarnings("RefusedBequest") + @Override + public long lastModified() { + return modificationTime; + } + } +} diff --git a/code/src/main/java/org/nocturne/module/ResourceLoader.java b/code/src/main/java/org/nocturne/module/ResourceLoader.java index 926d4be..af1d22f 100644 --- a/code/src/main/java/org/nocturne/module/ResourceLoader.java +++ b/code/src/main/java/org/nocturne/module/ResourceLoader.java @@ -1,16 +1,16 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.module; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Internal interface to get resources as streams by their names. - * - * @author Mike Mirzayanov - */ -public interface ResourceLoader { - InputStream getResourceInputStream(String path) throws IOException; -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.module; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Internal interface to get resources as streams by their names. + * + * @author Mike Mirzayanov + */ +public interface ResourceLoader { + InputStream getResourceInputStream(String path) throws IOException; +} diff --git a/code/src/main/java/org/nocturne/pool/PagePool.java b/code/src/main/java/org/nocturne/pool/PagePool.java index 1ff3011..8d73441 100644 --- a/code/src/main/java/org/nocturne/pool/PagePool.java +++ b/code/src/main/java/org/nocturne/pool/PagePool.java @@ -1,52 +1,52 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.pool; - -import org.apache.log4j.Logger; -import org.nocturne.main.Page; -import org.nocturne.main.PageLoader; - -/** - * Stores all the instances of the specific page class. - * - * @author Mike Mirzayanov - */ -public class PagePool extends Pool { - private static final Logger logger = Logger.getLogger(PagePool.class); - - /** - * Generates page instances. - */ - private final PageLoader pageLoader; - - /** */ - private final String pageClassName; - - /** - * Constructor PagePool creates a new PagePool instance. - * - * @param pageLoader of type PageLoader - * @param pageClassName of type String - */ - public PagePool(PageLoader pageLoader, String pageClassName) { - this.pageLoader = pageLoader; - this.pageClassName = pageClassName; - } - - /** - * {@inheritDoc} - */ - @Override - protected Page newInstance() { - return pageLoader.loadPage(pageClassName); - } - - /** - * {@inheritDoc} - */ - @Override - protected int getAcquireIncrement() { - return 3; - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.pool; + +import org.apache.log4j.Logger; +import org.nocturne.main.Page; +import org.nocturne.main.PageLoader; + +/** + * Stores all the instances of the specific page class. + * + * @author Mike Mirzayanov + */ +public class PagePool extends Pool { + private static final Logger logger = Logger.getLogger(PagePool.class); + + /** + * Generates page instances. + */ + private final PageLoader pageLoader; + + /** */ + private final String pageClassName; + + /** + * Constructor PagePool creates a new PagePool instance. + * + * @param pageLoader of type PageLoader + * @param pageClassName of type String + */ + public PagePool(PageLoader pageLoader, String pageClassName) { + this.pageLoader = pageLoader; + this.pageClassName = pageClassName; + } + + /** + * {@inheritDoc} + */ + @Override + protected Page newInstance() { + return pageLoader.loadPage(pageClassName); + } + + /** + * {@inheritDoc} + */ + @Override + protected int getAcquireIncrement() { + return 3; + } +} diff --git a/code/src/main/java/org/nocturne/pool/Pool.java b/code/src/main/java/org/nocturne/pool/Pool.java index f990e4b..545c039 100644 --- a/code/src/main/java/org/nocturne/pool/Pool.java +++ b/code/src/main/java/org/nocturne/pool/Pool.java @@ -1,114 +1,114 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.pool; - -import org.apache.log4j.Logger; - -import java.util.LinkedList; -import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Generic class for any pool. - * - * @author Mike Mirzayanov - */ -public abstract class Pool { - private static final Logger logger = Logger.getLogger(Pool.class); - private final Queue instances = new LinkedList<>(); - private final AtomicInteger createdCount = new AtomicInteger(); - - /** - * Override it to define the method how pool should get new instance. - * - * @return New instance. - */ - protected abstract T newInstance(); - - /** - * @return Number of new instances created each time the pool is empty. - */ - protected int getAcquireIncrement() { - return 5; - } - - /** - * Close() method will force finalizeInstance() for each - * pooled instance. - * - * @param t Instance to be finalized. - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void finalizeInstance(T t) { - // No operations. - } - - /** - * @return Extracts instance from the pool. Creates new instance if internal pool - * storage is empty. - */ - public T getInstance() { - synchronized (instances) { - checkSize(); - ensureElement(); - return instances.remove(); - } - } - - private void ensureElement() { - if (instances.isEmpty()) { - int acquireIncrement = getAcquireIncrement(); - for (int i = 0; i < acquireIncrement; i++) { - T instance = newInstance(); - instances.add(instance); - createdCount.incrementAndGet(); - } - } - } - - private void checkSize() { - int acquireIncrement = getAcquireIncrement(); - if (instances.size() > 4 * acquireIncrement) { - T t = instances.peek(); - if (t != null) { - logger.warn("Pool queue '" + getClass().getName() + "' [t=" + t.getClass().getName() + "] is too large."); - } - while (instances.size() > 2 * acquireIncrement) { - T instance = instances.remove(); - finalizeInstance(instance); - } - } - } - - /** - * Instances can be returned into the pool for future reusage. - * - * @param instance Instance to be returned into the pool. - */ - public void release(T instance) { - synchronized (instances) { - instances.add(instance); - } - } - - /** - * Finalizes all the instances in the pool and deletes them from the - * internal storage of the pool. - */ - public void close() { - synchronized (instances) { - while (!instances.isEmpty()) { - T instance = instances.remove(); - finalizeInstance(instance); - } - } - } - - /** - * @return Total count of the created instances by this pool. - */ - public int getCreatedCount() { - return createdCount.get(); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.pool; + +import org.apache.log4j.Logger; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Generic class for any pool. + * + * @author Mike Mirzayanov + */ +public abstract class Pool { + private static final Logger logger = Logger.getLogger(Pool.class); + private final Queue instances = new LinkedList<>(); + private final AtomicInteger createdCount = new AtomicInteger(); + + /** + * Override it to define the method how pool should get new instance. + * + * @return New instance. + */ + protected abstract T newInstance(); + + /** + * @return Number of new instances created each time the pool is empty. + */ + protected int getAcquireIncrement() { + return 5; + } + + /** + * Close() method will force finalizeInstance() for each + * pooled instance. + * + * @param t Instance to be finalized. + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void finalizeInstance(T t) { + // No operations. + } + + /** + * @return Extracts instance from the pool. Creates new instance if internal pool + * storage is empty. + */ + public T getInstance() { + synchronized (instances) { + checkSize(); + ensureElement(); + return instances.remove(); + } + } + + private void ensureElement() { + if (instances.isEmpty()) { + int acquireIncrement = getAcquireIncrement(); + for (int i = 0; i < acquireIncrement; i++) { + T instance = newInstance(); + instances.add(instance); + createdCount.incrementAndGet(); + } + } + } + + private void checkSize() { + int acquireIncrement = getAcquireIncrement(); + if (instances.size() > 4 * acquireIncrement) { + T t = instances.peek(); + if (t != null) { + logger.warn("Pool queue '" + getClass().getName() + "' [t=" + t.getClass().getName() + "] is too large."); + } + while (instances.size() > 2 * acquireIncrement) { + T instance = instances.remove(); + finalizeInstance(instance); + } + } + } + + /** + * Instances can be returned into the pool for future reusage. + * + * @param instance Instance to be returned into the pool. + */ + public void release(T instance) { + synchronized (instances) { + instances.add(instance); + } + } + + /** + * Finalizes all the instances in the pool and deletes them from the + * internal storage of the pool. + */ + public void close() { + synchronized (instances) { + while (!instances.isEmpty()) { + T instance = instances.remove(); + finalizeInstance(instance); + } + } + } + + /** + * @return Total count of the created instances by this pool. + */ + public int getCreatedCount() { + return createdCount.get(); + } +} diff --git a/code/src/main/java/org/nocturne/pool/TemplateEngineConfigurationPool.java b/code/src/main/java/org/nocturne/pool/TemplateEngineConfigurationPool.java index f5dfdf9..49436fc 100644 --- a/code/src/main/java/org/nocturne/pool/TemplateEngineConfigurationPool.java +++ b/code/src/main/java/org/nocturne/pool/TemplateEngineConfigurationPool.java @@ -1,74 +1,74 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.pool; - -import freemarker.template.Configuration; -import freemarker.template.DefaultObjectWrapper; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.nocturne.main.ApplicationTemplateLoader; -import org.nocturne.main.Constants; -import org.nocturne.main.ReloadingContext; - -import javax.servlet.FilterConfig; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Storage to store template configurations. - * Nocturne will not create new configuration on request but reuses old (if exists). - * - * @author Mike Mirzayanov - */ -public class TemplateEngineConfigurationPool extends Pool { - private static final Logger logger = Logger.getLogger(TemplateEngineConfigurationPool.class); - - private final FilterConfig filterConfig; - - private static final AtomicLong count = new AtomicLong(0); - private volatile TemplateEngineConfigurationHandler handler; - - public TemplateEngineConfigurationPool(FilterConfig filterConfig) { - this.filterConfig = filterConfig; - this.handler = null; - } - - public void setInstanceHandler(TemplateEngineConfigurationHandler handler) { - this.handler = handler; - } - - @Override - protected Configuration newInstance() { - Configuration templateEngineConfiguration = new Configuration(Constants.FREEMARKER_VERSION); - templateEngineConfiguration.setDefaultEncoding(StandardCharsets.UTF_8.name()); - - if (!ReloadingContext.getInstance().isDebug()) { - int templatesUpdateDelaySeconds = ReloadingContext.getInstance().getTemplatesUpdateDelay(); - - templateEngineConfiguration.setTemplateUpdateDelayMilliseconds( - TimeUnit.SECONDS.toMillis(templatesUpdateDelaySeconds) - ); - - logger.log(templatesUpdateDelaySeconds > 0 ? Level.INFO : Level.WARN, String.format( - "Processed templateEngineConfiguration.setTemplateUpdateDelay(%d sec).", templatesUpdateDelaySeconds - )); - } - - templateEngineConfiguration.setTemplateLoader(new ApplicationTemplateLoader()); - templateEngineConfiguration.setObjectWrapper(new DefaultObjectWrapper(Constants.FREEMARKER_VERSION)); - - logger.debug("Created instance of Configuration [count=" + count.incrementAndGet() + "]."); - - if (handler != null) { - handler.onInstance(templateEngineConfiguration); - } - - return templateEngineConfiguration; - } - - public interface TemplateEngineConfigurationHandler { - void onInstance(Configuration configuration); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.pool; + +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.nocturne.main.ApplicationTemplateLoader; +import org.nocturne.main.Constants; +import org.nocturne.main.ReloadingContext; + +import javax.servlet.FilterConfig; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Storage to store template configurations. + * Nocturne will not create new configuration on request but reuses old (if exists). + * + * @author Mike Mirzayanov + */ +public class TemplateEngineConfigurationPool extends Pool { + private static final Logger logger = Logger.getLogger(TemplateEngineConfigurationPool.class); + + private final FilterConfig filterConfig; + + private static final AtomicLong count = new AtomicLong(0); + private volatile TemplateEngineConfigurationHandler handler; + + public TemplateEngineConfigurationPool(FilterConfig filterConfig) { + this.filterConfig = filterConfig; + this.handler = null; + } + + public void setInstanceHandler(TemplateEngineConfigurationHandler handler) { + this.handler = handler; + } + + @Override + protected Configuration newInstance() { + Configuration templateEngineConfiguration = new Configuration(Constants.FREEMARKER_VERSION); + templateEngineConfiguration.setDefaultEncoding(StandardCharsets.UTF_8.name()); + + if (!ReloadingContext.getInstance().isDebug()) { + int templatesUpdateDelaySeconds = ReloadingContext.getInstance().getTemplatesUpdateDelay(); + + templateEngineConfiguration.setTemplateUpdateDelayMilliseconds( + TimeUnit.SECONDS.toMillis(templatesUpdateDelaySeconds) + ); + + logger.log(templatesUpdateDelaySeconds > 0 ? Level.INFO : Level.WARN, String.format( + "Processed templateEngineConfiguration.setTemplateUpdateDelay(%d sec).", templatesUpdateDelaySeconds + )); + } + + templateEngineConfiguration.setTemplateLoader(new ApplicationTemplateLoader()); + templateEngineConfiguration.setObjectWrapper(new DefaultObjectWrapper(Constants.FREEMARKER_VERSION)); + + logger.debug("Created instance of Configuration [count=" + count.incrementAndGet() + "]."); + + if (handler != null) { + handler.onInstance(templateEngineConfiguration); + } + + return templateEngineConfiguration; + } + + public interface TemplateEngineConfigurationHandler { + void onInstance(Configuration configuration); + } +} diff --git a/code/src/main/java/org/nocturne/postprocess/ResponsePostprocessor.java b/code/src/main/java/org/nocturne/postprocess/ResponsePostprocessor.java index dca1c9b..58c5fc1 100644 --- a/code/src/main/java/org/nocturne/postprocess/ResponsePostprocessor.java +++ b/code/src/main/java/org/nocturne/postprocess/ResponsePostprocessor.java @@ -1,14 +1,14 @@ -package org.nocturne.postprocess; - -import org.nocturne.main.Page; - -/** - * Use this interface to postprocess ready to render html from the page. - * To use it, just configure your Google Guice module to bind it to - * implementation. - * - * @author MikeMirzayanov (mirzayanovmr@gmail.com) - */ -public interface ResponsePostprocessor { - String postprocess(Page page, String postprocess); -} +package org.nocturne.postprocess; + +import org.nocturne.main.Page; + +/** + * Use this interface to postprocess ready to render html from the page. + * To use it, just configure your Google Guice module to bind it to + * implementation. + * + * @author MikeMirzayanov (mirzayanovmr@gmail.com) + */ +public interface ResponsePostprocessor { + String postprocess(Page page, String postprocess); +} diff --git a/code/src/main/java/org/nocturne/prometheus/Prometheus.java b/code/src/main/java/org/nocturne/prometheus/Prometheus.java index 33f9a44..15f167d 100644 --- a/code/src/main/java/org/nocturne/prometheus/Prometheus.java +++ b/code/src/main/java/org/nocturne/prometheus/Prometheus.java @@ -1,42 +1,42 @@ -package org.nocturne.prometheus; - -import io.prometheus.client.Counter; -import io.prometheus.client.Summary; - -public class Prometheus { - private static final Counter PAGES_COUNTER = Counter.build() - .name("nocturne_pages_total").help("Nocturne pages total count") - .labelNames("className") - .register(); - - private static final Summary PAGES_LATENCY_SECONDS = Summary.build() - .name("nocturne_pages_latency_seconds").help("Nocturne pages latency in seconds") - .labelNames("className", "phase") - .register(); - - private static final Counter FRAMES_COUNTER = Counter.build() - .name("nocturne_frames_total").help("Nocturne frames total count") - .labelNames("className") - .register(); - - private static final Summary FRAMES_LATENCY_SECONDS = Summary.build() - .name("nocturne_frames_latency_seconds").help("Nocturne frames latency in seconds") - .labelNames("className", "phase") - .register(); - - public static Counter getPagesCounter() { - return PAGES_COUNTER; - } - - public static Summary getPagesLatencySeconds() { - return PAGES_LATENCY_SECONDS; - } - - public static Counter getFramesCounter() { - return FRAMES_COUNTER; - } - - public static Summary getFramesLatencySeconds() { - return FRAMES_LATENCY_SECONDS; - } -} +package org.nocturne.prometheus; + +import io.prometheus.client.Counter; +import io.prometheus.client.Summary; + +public class Prometheus { + private static final Counter PAGES_COUNTER = Counter.build() + .name("nocturne_pages_total").help("Nocturne pages total count") + .labelNames("className") + .register(); + + private static final Summary PAGES_LATENCY_SECONDS = Summary.build() + .name("nocturne_pages_latency_seconds").help("Nocturne pages latency in seconds") + .labelNames("className", "phase") + .register(); + + private static final Counter FRAMES_COUNTER = Counter.build() + .name("nocturne_frames_total").help("Nocturne frames total count") + .labelNames("className") + .register(); + + private static final Summary FRAMES_LATENCY_SECONDS = Summary.build() + .name("nocturne_frames_latency_seconds").help("Nocturne frames latency in seconds") + .labelNames("className", "phase") + .register(); + + public static Counter getPagesCounter() { + return PAGES_COUNTER; + } + + public static Summary getPagesLatencySeconds() { + return PAGES_LATENCY_SECONDS; + } + + public static Counter getFramesCounter() { + return FRAMES_COUNTER; + } + + public static Summary getFramesLatencySeconds() { + return FRAMES_LATENCY_SECONDS; + } +} diff --git a/code/src/main/java/org/nocturne/reset/ComponentFieldsResetter.java b/code/src/main/java/org/nocturne/reset/ComponentFieldsResetter.java index 85ebc20..21d67be 100644 --- a/code/src/main/java/org/nocturne/reset/ComponentFieldsResetter.java +++ b/code/src/main/java/org/nocturne/reset/ComponentFieldsResetter.java @@ -1,21 +1,21 @@ -package org.nocturne.reset; - -import org.nocturne.main.Component; -import org.nocturne.main.Frame; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -public class ComponentFieldsResetter extends FieldsResetter { - public ComponentFieldsResetter(Component component) { - super(component); - } - - @Override - boolean isResetStopClass(Class clazz) { - return clazz.getCanonicalName().equals(Component.class.getCanonicalName()) - || clazz.getCanonicalName().equals(Page.class.getCanonicalName()) - || clazz.getCanonicalName().equals(Frame.class.getCanonicalName()); - } -} +package org.nocturne.reset; + +import org.nocturne.main.Component; +import org.nocturne.main.Frame; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +public class ComponentFieldsResetter extends FieldsResetter { + public ComponentFieldsResetter(Component component) { + super(component); + } + + @Override + boolean isResetStopClass(Class clazz) { + return clazz.getCanonicalName().equals(Component.class.getCanonicalName()) + || clazz.getCanonicalName().equals(Page.class.getCanonicalName()) + || clazz.getCanonicalName().equals(Frame.class.getCanonicalName()); + } +} diff --git a/code/src/main/java/org/nocturne/reset/FieldsResetter.java b/code/src/main/java/org/nocturne/reset/FieldsResetter.java index eb500d0..eadfd91 100644 --- a/code/src/main/java/org/nocturne/reset/FieldsResetter.java +++ b/code/src/main/java/org/nocturne/reset/FieldsResetter.java @@ -1,174 +1,174 @@ -package org.nocturne.reset; - -import org.nocturne.exception.ConfigurationException; -import org.nocturne.main.ApplicationContext; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * @author Mike Mirzayanov - */ -abstract class FieldsResetter { - private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(FieldsResetter.class); - - private static final Map, Object> PRIMITIVES_DEFAULT_VALUES = new ConcurrentHashMap<>(); - private static final ConcurrentMap RESET_ANNOTATIONS_CACHE = new ConcurrentHashMap<>(); - private static final ConcurrentMap PERSIST_ANNOTATIONS_CACHE = new ConcurrentHashMap<>(); - - private final Object object; - private final List fieldsToReset = new ArrayList<>(); - private final ResetStrategy resetStrategy; - - private static boolean hasResetAnnotation(AnnotatedElement annotatedElement) { - Boolean result = RESET_ANNOTATIONS_CACHE.get(annotatedElement); - if (result != null) { - return result; - } - - result = false; - Annotation[] annotations = annotatedElement.getAnnotations(); - for (Annotation annotation : annotations) { - if (ApplicationContext.getInstance().getResetAnnotations().contains(annotation.annotationType().getName())) { - result = true; - break; - } - } - - RESET_ANNOTATIONS_CACHE.putIfAbsent(annotatedElement, result); - return result; - } - - private static boolean hasPersistAnnotation(AnnotatedElement annotatedElement) { - Boolean result = PERSIST_ANNOTATIONS_CACHE.get(annotatedElement); - if (result != null) { - return result; - } - - result = false; - Annotation[] annotations = annotatedElement.getAnnotations(); - for (Annotation annotation : annotations) { - if (ApplicationContext.getInstance().getPersistAnnotations().contains(annotation.annotationType().getName())) { - result = true; - break; - } - } - - PERSIST_ANNOTATIONS_CACHE.putIfAbsent(annotatedElement, result); - return result; - } - - public FieldsResetter(Object object) { - resetStrategy = getStrategy(ApplicationContext.getInstance().getResetStrategy(), - hasResetAnnotation(object.getClass()), - hasPersistAnnotation(object.getClass()), - object.getClass().getCanonicalName()); - - this.object = object; - addFieldsToReset(); - } - - private boolean isGuiceOrCglibField(Field field) { - return field.getName().contains("$") && (field.getDeclaringClass().getName().contains("$$") - || field.getDeclaringClass().getName().contains("EnhancerByGuice")); - } - - private void addFieldsToReset() { - Class clazz = object.getClass(); - while (!isResetStopClass(clazz)) { - Field[] declaredFields = clazz.getDeclaredFields(); - for (Field declaredField : declaredFields) { - if (Modifier.isStatic(declaredField.getModifiers()) - || Modifier.isFinal(declaredField.getModifiers()) - || isGuiceOrCglibField(declaredField)) { - continue; - } - - ResetStrategy fieldStrategy = getStrategy( - resetStrategy, - hasResetAnnotation(declaredField), hasPersistAnnotation(declaredField), - declaredField.toString() - ); - - if (fieldStrategy == ResetStrategy.RESET) { - fieldsToReset.add(declaredField); - } - } - clazz = clazz.getSuperclass(); - } - } - - abstract boolean isResetStopClass(Class clazz); - - public void resetFields() { - for (Field field : fieldsToReset) { - resetField(field); - } - } - - private void resetField(Field field) { - boolean accessible = field.isAccessible(); - try { - field.setAccessible(true); - if (field.getType().isPrimitive()) { - resetPrimitiveField(field); - return; - } - - try { - field.set(object, null); - } catch (IllegalAccessException ignored) { - // No operations. - } - } finally { - field.setAccessible(accessible); - } - } - - private void resetPrimitiveField(Field field) { - Class type = field.getType(); - if (type != void.class) { - try { - field.set(object, PRIMITIVES_DEFAULT_VALUES.get(type)); - } catch (IllegalAccessException ignored) { - // No operations. - } - } - } - - private static ResetStrategy getStrategy( - ResetStrategy defaultStrategy, boolean hasReset, boolean hasPersist, String name) { - if (hasPersist && hasReset) { - logger.error("It is impossible to use Reset and Persist at the same time [name=" + name + "]."); - throw new ConfigurationException("It is impossible to use " + - "Reset and Persist at the same time [name=" + name + "]."); - } - - if (hasReset) { - return ResetStrategy.RESET; - } - - if (hasPersist) { - return ResetStrategy.PERSIST; - } - - return defaultStrategy; - } - - static { - PRIMITIVES_DEFAULT_VALUES.put(int.class, 0); - PRIMITIVES_DEFAULT_VALUES.put(long.class, 0L); - PRIMITIVES_DEFAULT_VALUES.put(double.class, 0.0D); - PRIMITIVES_DEFAULT_VALUES.put(float.class, 0.0F); - PRIMITIVES_DEFAULT_VALUES.put(byte.class, (byte) 0); - PRIMITIVES_DEFAULT_VALUES.put(short.class, (short) 0); - PRIMITIVES_DEFAULT_VALUES.put(boolean.class, false); - } -} +package org.nocturne.reset; + +import org.nocturne.exception.ConfigurationException; +import org.nocturne.main.ApplicationContext; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Mike Mirzayanov + */ +abstract class FieldsResetter { + private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(FieldsResetter.class); + + private static final Map, Object> PRIMITIVES_DEFAULT_VALUES = new ConcurrentHashMap<>(); + private static final ConcurrentMap RESET_ANNOTATIONS_CACHE = new ConcurrentHashMap<>(); + private static final ConcurrentMap PERSIST_ANNOTATIONS_CACHE = new ConcurrentHashMap<>(); + + private final Object object; + private final List fieldsToReset = new ArrayList<>(); + private final ResetStrategy resetStrategy; + + private static boolean hasResetAnnotation(AnnotatedElement annotatedElement) { + Boolean result = RESET_ANNOTATIONS_CACHE.get(annotatedElement); + if (result != null) { + return result; + } + + result = false; + Annotation[] annotations = annotatedElement.getAnnotations(); + for (Annotation annotation : annotations) { + if (ApplicationContext.getInstance().getResetAnnotations().contains(annotation.annotationType().getName())) { + result = true; + break; + } + } + + RESET_ANNOTATIONS_CACHE.putIfAbsent(annotatedElement, result); + return result; + } + + private static boolean hasPersistAnnotation(AnnotatedElement annotatedElement) { + Boolean result = PERSIST_ANNOTATIONS_CACHE.get(annotatedElement); + if (result != null) { + return result; + } + + result = false; + Annotation[] annotations = annotatedElement.getAnnotations(); + for (Annotation annotation : annotations) { + if (ApplicationContext.getInstance().getPersistAnnotations().contains(annotation.annotationType().getName())) { + result = true; + break; + } + } + + PERSIST_ANNOTATIONS_CACHE.putIfAbsent(annotatedElement, result); + return result; + } + + public FieldsResetter(Object object) { + resetStrategy = getStrategy(ApplicationContext.getInstance().getResetStrategy(), + hasResetAnnotation(object.getClass()), + hasPersistAnnotation(object.getClass()), + object.getClass().getCanonicalName()); + + this.object = object; + addFieldsToReset(); + } + + private boolean isGuiceOrCglibField(Field field) { + return field.getName().contains("$") && (field.getDeclaringClass().getName().contains("$$") + || field.getDeclaringClass().getName().contains("EnhancerByGuice")); + } + + private void addFieldsToReset() { + Class clazz = object.getClass(); + while (!isResetStopClass(clazz)) { + Field[] declaredFields = clazz.getDeclaredFields(); + for (Field declaredField : declaredFields) { + if (Modifier.isStatic(declaredField.getModifiers()) + || Modifier.isFinal(declaredField.getModifiers()) + || isGuiceOrCglibField(declaredField)) { + continue; + } + + ResetStrategy fieldStrategy = getStrategy( + resetStrategy, + hasResetAnnotation(declaredField), hasPersistAnnotation(declaredField), + declaredField.toString() + ); + + if (fieldStrategy == ResetStrategy.RESET) { + fieldsToReset.add(declaredField); + } + } + clazz = clazz.getSuperclass(); + } + } + + abstract boolean isResetStopClass(Class clazz); + + public void resetFields() { + for (Field field : fieldsToReset) { + resetField(field); + } + } + + private void resetField(Field field) { + boolean accessible = field.isAccessible(); + try { + field.setAccessible(true); + if (field.getType().isPrimitive()) { + resetPrimitiveField(field); + return; + } + + try { + field.set(object, null); + } catch (IllegalAccessException ignored) { + // No operations. + } + } finally { + field.setAccessible(accessible); + } + } + + private void resetPrimitiveField(Field field) { + Class type = field.getType(); + if (type != void.class) { + try { + field.set(object, PRIMITIVES_DEFAULT_VALUES.get(type)); + } catch (IllegalAccessException ignored) { + // No operations. + } + } + } + + private static ResetStrategy getStrategy( + ResetStrategy defaultStrategy, boolean hasReset, boolean hasPersist, String name) { + if (hasPersist && hasReset) { + logger.error("It is impossible to use Reset and Persist at the same time [name=" + name + "]."); + throw new ConfigurationException("It is impossible to use " + + "Reset and Persist at the same time [name=" + name + "]."); + } + + if (hasReset) { + return ResetStrategy.RESET; + } + + if (hasPersist) { + return ResetStrategy.PERSIST; + } + + return defaultStrategy; + } + + static { + PRIMITIVES_DEFAULT_VALUES.put(int.class, 0); + PRIMITIVES_DEFAULT_VALUES.put(long.class, 0L); + PRIMITIVES_DEFAULT_VALUES.put(double.class, 0.0D); + PRIMITIVES_DEFAULT_VALUES.put(float.class, 0.0F); + PRIMITIVES_DEFAULT_VALUES.put(byte.class, (byte) 0); + PRIMITIVES_DEFAULT_VALUES.put(short.class, (short) 0); + PRIMITIVES_DEFAULT_VALUES.put(boolean.class, false); + } +} diff --git a/code/src/main/java/org/nocturne/reset/ObjectFieldsResetter.java b/code/src/main/java/org/nocturne/reset/ObjectFieldsResetter.java index c75487c..17255af 100644 --- a/code/src/main/java/org/nocturne/reset/ObjectFieldsResetter.java +++ b/code/src/main/java/org/nocturne/reset/ObjectFieldsResetter.java @@ -1,18 +1,18 @@ -package org.nocturne.reset; - -/** - * @author Mike Mirzayanov - */ -public class ObjectFieldsResetter extends FieldsResetter { - private final Class resetStopClass; - - public ObjectFieldsResetter(Object object, Class resetStopClass) { - super(object); - this.resetStopClass = resetStopClass; - } - - @Override - boolean isResetStopClass(Class clazz) { - return clazz == resetStopClass; - } -} +package org.nocturne.reset; + +/** + * @author Mike Mirzayanov + */ +public class ObjectFieldsResetter extends FieldsResetter { + private final Class resetStopClass; + + public ObjectFieldsResetter(Object object, Class resetStopClass) { + super(object); + this.resetStopClass = resetStopClass; + } + + @Override + boolean isResetStopClass(Class clazz) { + return clazz == resetStopClass; + } +} diff --git a/code/src/main/java/org/nocturne/reset/ResetStrategy.java b/code/src/main/java/org/nocturne/reset/ResetStrategy.java index 8044cc7..7e8e60a 100644 --- a/code/src/main/java/org/nocturne/reset/ResetStrategy.java +++ b/code/src/main/java/org/nocturne/reset/ResetStrategy.java @@ -1,9 +1,9 @@ -package org.nocturne.reset; - -/** - * @author Mike Mirzayanov - */ -public enum ResetStrategy { - PERSIST, - RESET -} +package org.nocturne.reset; + +/** + * @author Mike Mirzayanov + */ +public enum ResetStrategy { + PERSIST, + RESET +} diff --git a/code/src/main/java/org/nocturne/reset/annotation/Persist.java b/code/src/main/java/org/nocturne/reset/annotation/Persist.java index bae2e10..79e3f25 100644 --- a/code/src/main/java/org/nocturne/reset/annotation/Persist.java +++ b/code/src/main/java/org/nocturne/reset/annotation/Persist.java @@ -1,14 +1,14 @@ -package org.nocturne.reset.annotation; - -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.FIELD, ElementType.TYPE}) -public @interface Persist { -} +package org.nocturne.reset.annotation; + +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.FIELD, ElementType.TYPE}) +public @interface Persist { +} diff --git a/code/src/main/java/org/nocturne/reset/annotation/Reset.java b/code/src/main/java/org/nocturne/reset/annotation/Reset.java index e37e2b2..d27227b 100644 --- a/code/src/main/java/org/nocturne/reset/annotation/Reset.java +++ b/code/src/main/java/org/nocturne/reset/annotation/Reset.java @@ -1,14 +1,14 @@ -package org.nocturne.reset.annotation; - -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.FIELD, ElementType.TYPE}) -public @interface Reset { -} +package org.nocturne.reset.annotation; + +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.FIELD, ElementType.TYPE}) +public @interface Reset { +} diff --git a/code/src/main/java/org/nocturne/util/FileUtil.java b/code/src/main/java/org/nocturne/util/FileUtil.java index f908454..8df7425 100644 --- a/code/src/main/java/org/nocturne/util/FileUtil.java +++ b/code/src/main/java/org/nocturne/util/FileUtil.java @@ -1,524 +1,524 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.util; - -import com.google.common.primitives.Ints; -import org.apache.commons.io.IOUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.xml.sax.InputSource; - -import javax.servlet.ServletContext; -import javax.xml.namespace.QName; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.*; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; - -/** - * Utility class with some file operations. You can use it - * instead of apache version if you want. - * - * @author Mike Mirzayanov - */ -public class FileUtil { - /** - * Stores xpath factory. - */ - private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance(); - - /** - * Copies one file to another. Fails if the target exists. - * - * @param source Source file. - * @param destination Destination file. - * @throws IOException Can't perform copy. - */ - public static void copyFile(File source, File destination) throws IOException { - if (!source.isFile()) { - throw new IOException(source + " is not a file."); - } - - if (destination.exists()) { - throw new IOException("Destination file " + destination + " is already exist."); - } - - FileChannel inChannel = new FileInputStream(source).getChannel(); - FileChannel outChannel = new FileOutputStream(destination).getChannel(); - - try { - inChannel.transferTo(0, inChannel.size(), outChannel); - } finally { - inChannel.close(); - outChannel.close(); - } - } - - /** - * Copy one directory into another. If the second one exists it copies nested files from - * the source to destination. - * - * @param source Source directory. - * @param destination Destination directory. - * @throws IOException when can't perform copy. - */ - public static void copyDirectory(File source, File destination) throws IOException { - if (!source.isDirectory()) { - throw new IOException(source + " is not a directory."); - } - - if (destination.isFile()) { - throw new IOException(destination + " is a file."); - } - - if (!destination.exists()) { - if (!destination.mkdirs()) { - throw new IOException("Can't create " + destination + '.'); - } - } - - String[] children = source.list(); - - for (String child : children) { - File nextSource = new File(source, child); - File nextDestination = new File(destination, child); - if (nextSource.isDirectory()) { - copyDirectory(nextSource, nextDestination); - } else { - copyFile(nextSource, nextDestination); - } - } - } - - /** - * Deletes file or directory. Finishes quitely in case of no such file. - * Directory will be deleted with each nested element. - * - * @param file File to be deleted. - * @throws IOException if can't delete file. - */ - public static void deleteTotaly(File file) throws IOException { - if (file.exists()) { - if (file.isFile()) { - if (!file.delete()) { - throw new IOException("Can't delete " + file + '.'); - } - } else { - String[] children = file.list(); - for (String child : children) { - deleteTotaly(new File(file, child)); - } - if (!file.delete()) { - throw new IOException("Can't delete " + file + '.'); - } - } - } - } - - - /** - * Deletes file or directory. Finishes quitely in _any_ case. - * Will start new thread. - * - * @param file File to be deleted. - */ - public static void deleteTotalyAsync(File file) { - new Thread(new Runnable() { - @Override - public void run() { - try { - deleteTotaly(file); - } catch (Throwable ignored) { - // No operations. - } - } - }).start(); - } - - /** - * @param reader Reader to be processed. - * @return String containing all characters from reader. - * @throws IOException if can't read data. - */ - public static String readFromReader(Reader reader) throws IOException { - StringBuilder result = new StringBuilder(); - try { - char[] chunk = new char[65536]; - while (true) { - int size = reader.read(chunk); - if (size == -1) { - break; - } - result.append(chunk, 0, size); - } - } catch (IOException e) { - throw new IOException("Can't read from reader.", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (Exception ignored) { - // No operations. - } - } - } - return result.toString(); - } - - /** - * @param file File to be read. - * @return String containing file data. - * @throws IOException if can't read file. Possibly, file parameter - * doesn't exists, is directory or not enough permissions. - */ - public static String readFile(File file) throws IOException { - return readFromReader(new FileReader(file)); - } - - private static void ensureParentDirectoryExists(File file) throws IOException { - File parent = file.getParentFile(); - if (!parent.isDirectory() && !parent.mkdirs()) { - throw new IOException("Can't create directory " + parent + '.'); - } - } - - /** - * Writes new file into filesystem. Overwrite existing if exists. - * Creates parent directory if needed. - * - * @param file File to be write. - * @param content Content to be write. - * @throws IOException if can't read file. - */ - public static void writeFile(File file, String content) throws IOException { - ensureParentDirectoryExists(file); - - try (FileWriter writer = new FileWriter(file)) { - writer.write(content); - } - } - - /** - * Writes new file into filesystem. Overwrite existing if exists. - * Creates parent directory if needed. - * - * @param file File to be write. - * @param bytes Bytes to be write. - * @throws IOException if can't write file. - */ - public static void writeFile(File file, byte[] bytes) throws IOException { - ensureParentDirectoryExists(file); - - FileOutputStream outputStream = new FileOutputStream(file); - outputStream.write(bytes); - outputStream.close(); - } - - /** - * Very like to writeFile but doesn't overwrite file. - * Creates parent directory if needed. - * - * @param file File to write. - * @param bytes Bytes to write into file. - * @throws IOException If file exists or can't write file. - */ - public static void createFile(File file, byte[] bytes) throws IOException { - if (file.exists()) { - throw new IOException("File exists " + file); - } - - writeFile(file, bytes); - } - - /** - * Very like to writeFile but doesn't overwrite file. - * - * @param file File to write. - * @param content String to write into file. - * @throws IOException If file exists or can't write file. - */ - public static void createFile(File file, String content) throws IOException { - if (file.exists()) { - throw new IOException("File exists " + file); - } - - writeFile(file, content); - } - - /** - * Parses XML string and extracts value. - * - * @param xml InputStream containing xml document. - * @param xpath Xpath expression. - * @param clazz String.class or Integer.class are supported now. - * @param Return type. - * @return Return value. - */ - @SuppressWarnings({"unchecked"}) - public static T extractFromXml(InputStream xml, String xpath, Class clazz) { - XPath xp = XPATH_FACTORY.newXPath(); - - QName type = null; - - if (clazz.equals(String.class)) { - type = XPathConstants.STRING; - } - - if (clazz.equals(Integer.class)) { - type = XPathConstants.NUMBER; - } - - if (type == null) { - throw new IllegalArgumentException("Illegal clazz."); - } - - try { - XPathExpression expression = xp.compile(xpath); - - Object result = expression.evaluate(new InputSource(xml), type); - if (type == XPathConstants.NUMBER) { - result = ((Double) result).intValue(); - return (T) result; - } else { - return (T) result; - } - } catch (XPathExpressionException e) { - throw new IllegalArgumentException("Illegal xpath.", e); - } - } - - /** - * Writes XML document into file. - * - * @param file File to write. - * @param document XML document. - */ - public static void writeXml(File file, Document document) { - Source source = new DOMSource(document); - try { - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); - StreamResult result = new StreamResult(new FileOutputStream(file)); - transformer.transform(source, result); - result.getOutputStream().close(); - } catch (TransformerConfigurationException e) { - throw new IllegalArgumentException("Transformer configuration is illegal.", e); - } catch (TransformerException e) { - throw new IllegalArgumentException("Transformer failed.", e); - } catch (IOException e) { - throw new IllegalArgumentException("Can't perform IO.", e); - } - } - - /** - * Changes the value describing by xpath to specific value. And updates file. - * - * @param file Which will read first and updated later. - * @param xpath Xpath to find specific Node. - * @param value Value to be set for found node. - */ - public static void updateXml(File file, String xpath, String value) { - XPath xp = XPATH_FACTORY.newXPath(); - - try { - XPathExpression root = xp.compile("/"); - Document document = (Document) root.evaluate(new InputSource(new FileInputStream(file)), XPathConstants.NODE); - XPathExpression nodeXpath = xp.compile(xpath); - Node node = (Node) nodeXpath.evaluate(document, XPathConstants.NODE); - node.setNodeValue(value); - writeXml(file, document); - } catch (XPathExpressionException e) { - throw new IllegalArgumentException("Illegal xpath.", e); - } catch (FileNotFoundException e) { - throw new IllegalArgumentException("Can't find file.", e); - } - } - - /** - * @param file File to remove. - * @throws IOException If file not found or can't be removed. - */ - public static void removeFile(File file) throws IOException { - if (!file.exists()) { - throw new IOException("File not found " + file + '.'); - } - - if (!file.delete()) { - throw new IOException("Can't delete " + file + '.'); - } - } - - public static byte[] _getBytes(File file) throws IOException { - int size = Ints.checkedCast(file.length()); - InputStream stream = new FileInputStream(file); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(size); - byte[] chunk = new byte[1048576]; - while (true) { - int byteCount = stream.read(chunk); - if (byteCount < 0) { - break; - } else { - byteArrayOutputStream.write(chunk, 0, byteCount); - } - } - stream.close(); - return byteArrayOutputStream.toByteArray(); - } - - /** - * @param file File to be read. - * @return File content as a byte array. - * @throws IOException if can't read file. - * @throws FileNotFoundException if can't find file. - */ - public static byte[] getBytes(File file) throws IOException { - if (file.isFile()) { - long size = file.length(); - FileInputStream stream = new FileInputStream(file); - FileChannel channel = stream.getChannel(); - ByteBuffer bytes = ByteBuffer.allocate(Ints.checkedCast(size)); - channel.read(bytes); - channel.close(); - stream.close(); - return bytes.array(); - } else { - throw new FileNotFoundException("Can't find " + file + '.'); - } - } - - /** - * Returns first 255 bytes of the file. Returns smaller number of bytes it it contains less. - * - * @param file File to be read. - * @return File content as a byte array. - * @throws IOException if can't read file. - * @throws FileNotFoundException if can't find file. - */ - public static FirstBytes getFirstBytes(File file) throws IOException { - if (file.isFile()) { - boolean truncated = false; - long size = file.length(); - if (size > 255) { - truncated = true; - size = 255; - } - FileInputStream stream = new FileInputStream(file); - FileChannel channel = stream.getChannel(); - ByteBuffer bytes = ByteBuffer.allocate(Ints.checkedCast(size)); - channel.read(bytes); - channel.close(); - stream.close(); - return new FirstBytes(truncated, bytes.array()); - } else { - throw new FileNotFoundException("Can't find " + file + '.'); - } - } - - /** - * Accepts not fidden files. - */ - public static class NotHiddenFileFilter implements FilenameFilter { - @Override - public boolean accept(File dir, String name) { - return !new File(dir, name).isHidden(); - } - } - - /** - * @param file Any file. - * @return String Name part (simple name without extension). - */ - public static String getName(File file) { - String name = file.getName(); - if (name.contains(".")) { - return name.substring(0, name.lastIndexOf('.')); - } else { - return name; - } - } - - /** - * @param file Any file. - * @return String Extension with dot in lowercase. For example, ".cpp". - */ - public static String getExt(File file) { - String name = file.getName(); - if (name.contains(".")) { - return name.substring(name.lastIndexOf('.')).toLowerCase(); - } else { - return ""; - } - } - - /** - * @param directory Directory to be processed. - * @return Total length of _all_ nested files in the directory. - */ - public static long getDirectorySize(File directory) { - if (!directory.isDirectory()) { - throw new IllegalArgumentException("Abstract path " + directory + " is not a directory."); - } - - long result = 0; - - File[] files = directory.listFiles(); - - for (File file : files) { - if (file.isFile()) { - result += file.length(); - } - - if (file.isDirectory()) { - result += getDirectorySize(file); - } - } - - return result; - } - - /** - * @param servletContext ServletContext. - * @param path String. - * @return As servletContext.getRealPath(path) (tomcats 7 and 8 work differently). - */ - public static String getRealPath(ServletContext servletContext, String path) { - String result = servletContext.getRealPath(path); - if (result == null) { - if (path.startsWith("/")) { - result = servletContext.getRealPath(path.substring(1)); - } else { - result = servletContext.getRealPath('/' + path); - } - } - - if (result == null && new File(path).isAbsolute()) { - return path; - } - - return result; - } - - public static class FirstBytes { - private final boolean truncated; - private final byte[] bytes; - - private FirstBytes(boolean truncated, byte[] bytes) { - this.truncated = truncated; - this.bytes = bytes; - } - - public boolean isTruncated() { - return truncated; - } - - public byte[] getBytes() { - return bytes; - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.util; + +import com.google.common.primitives.Ints; +import org.apache.commons.io.IOUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import javax.servlet.ServletContext; +import javax.xml.namespace.QName; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.*; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * Utility class with some file operations. You can use it + * instead of apache version if you want. + * + * @author Mike Mirzayanov + */ +public class FileUtil { + /** + * Stores xpath factory. + */ + private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance(); + + /** + * Copies one file to another. Fails if the target exists. + * + * @param source Source file. + * @param destination Destination file. + * @throws IOException Can't perform copy. + */ + public static void copyFile(File source, File destination) throws IOException { + if (!source.isFile()) { + throw new IOException(source + " is not a file."); + } + + if (destination.exists()) { + throw new IOException("Destination file " + destination + " is already exist."); + } + + FileChannel inChannel = new FileInputStream(source).getChannel(); + FileChannel outChannel = new FileOutputStream(destination).getChannel(); + + try { + inChannel.transferTo(0, inChannel.size(), outChannel); + } finally { + inChannel.close(); + outChannel.close(); + } + } + + /** + * Copy one directory into another. If the second one exists it copies nested files from + * the source to destination. + * + * @param source Source directory. + * @param destination Destination directory. + * @throws IOException when can't perform copy. + */ + public static void copyDirectory(File source, File destination) throws IOException { + if (!source.isDirectory()) { + throw new IOException(source + " is not a directory."); + } + + if (destination.isFile()) { + throw new IOException(destination + " is a file."); + } + + if (!destination.exists()) { + if (!destination.mkdirs()) { + throw new IOException("Can't create " + destination + '.'); + } + } + + String[] children = source.list(); + + for (String child : children) { + File nextSource = new File(source, child); + File nextDestination = new File(destination, child); + if (nextSource.isDirectory()) { + copyDirectory(nextSource, nextDestination); + } else { + copyFile(nextSource, nextDestination); + } + } + } + + /** + * Deletes file or directory. Finishes quitely in case of no such file. + * Directory will be deleted with each nested element. + * + * @param file File to be deleted. + * @throws IOException if can't delete file. + */ + public static void deleteTotaly(File file) throws IOException { + if (file.exists()) { + if (file.isFile()) { + if (!file.delete()) { + throw new IOException("Can't delete " + file + '.'); + } + } else { + String[] children = file.list(); + for (String child : children) { + deleteTotaly(new File(file, child)); + } + if (!file.delete()) { + throw new IOException("Can't delete " + file + '.'); + } + } + } + } + + + /** + * Deletes file or directory. Finishes quitely in _any_ case. + * Will start new thread. + * + * @param file File to be deleted. + */ + public static void deleteTotalyAsync(File file) { + new Thread(new Runnable() { + @Override + public void run() { + try { + deleteTotaly(file); + } catch (Throwable ignored) { + // No operations. + } + } + }).start(); + } + + /** + * @param reader Reader to be processed. + * @return String containing all characters from reader. + * @throws IOException if can't read data. + */ + public static String readFromReader(Reader reader) throws IOException { + StringBuilder result = new StringBuilder(); + try { + char[] chunk = new char[65536]; + while (true) { + int size = reader.read(chunk); + if (size == -1) { + break; + } + result.append(chunk, 0, size); + } + } catch (IOException e) { + throw new IOException("Can't read from reader.", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception ignored) { + // No operations. + } + } + } + return result.toString(); + } + + /** + * @param file File to be read. + * @return String containing file data. + * @throws IOException if can't read file. Possibly, file parameter + * doesn't exists, is directory or not enough permissions. + */ + public static String readFile(File file) throws IOException { + return readFromReader(new FileReader(file)); + } + + private static void ensureParentDirectoryExists(File file) throws IOException { + File parent = file.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Can't create directory " + parent + '.'); + } + } + + /** + * Writes new file into filesystem. Overwrite existing if exists. + * Creates parent directory if needed. + * + * @param file File to be write. + * @param content Content to be write. + * @throws IOException if can't read file. + */ + public static void writeFile(File file, String content) throws IOException { + ensureParentDirectoryExists(file); + + try (FileWriter writer = new FileWriter(file)) { + writer.write(content); + } + } + + /** + * Writes new file into filesystem. Overwrite existing if exists. + * Creates parent directory if needed. + * + * @param file File to be write. + * @param bytes Bytes to be write. + * @throws IOException if can't write file. + */ + public static void writeFile(File file, byte[] bytes) throws IOException { + ensureParentDirectoryExists(file); + + FileOutputStream outputStream = new FileOutputStream(file); + outputStream.write(bytes); + outputStream.close(); + } + + /** + * Very like to writeFile but doesn't overwrite file. + * Creates parent directory if needed. + * + * @param file File to write. + * @param bytes Bytes to write into file. + * @throws IOException If file exists or can't write file. + */ + public static void createFile(File file, byte[] bytes) throws IOException { + if (file.exists()) { + throw new IOException("File exists " + file); + } + + writeFile(file, bytes); + } + + /** + * Very like to writeFile but doesn't overwrite file. + * + * @param file File to write. + * @param content String to write into file. + * @throws IOException If file exists or can't write file. + */ + public static void createFile(File file, String content) throws IOException { + if (file.exists()) { + throw new IOException("File exists " + file); + } + + writeFile(file, content); + } + + /** + * Parses XML string and extracts value. + * + * @param xml InputStream containing xml document. + * @param xpath Xpath expression. + * @param clazz String.class or Integer.class are supported now. + * @param Return type. + * @return Return value. + */ + @SuppressWarnings({"unchecked"}) + public static T extractFromXml(InputStream xml, String xpath, Class clazz) { + XPath xp = XPATH_FACTORY.newXPath(); + + QName type = null; + + if (clazz.equals(String.class)) { + type = XPathConstants.STRING; + } + + if (clazz.equals(Integer.class)) { + type = XPathConstants.NUMBER; + } + + if (type == null) { + throw new IllegalArgumentException("Illegal clazz."); + } + + try { + XPathExpression expression = xp.compile(xpath); + + Object result = expression.evaluate(new InputSource(xml), type); + if (type == XPathConstants.NUMBER) { + result = ((Double) result).intValue(); + return (T) result; + } else { + return (T) result; + } + } catch (XPathExpressionException e) { + throw new IllegalArgumentException("Illegal xpath.", e); + } + } + + /** + * Writes XML document into file. + * + * @param file File to write. + * @param document XML document. + */ + public static void writeXml(File file, Document document) { + Source source = new DOMSource(document); + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); + StreamResult result = new StreamResult(new FileOutputStream(file)); + transformer.transform(source, result); + result.getOutputStream().close(); + } catch (TransformerConfigurationException e) { + throw new IllegalArgumentException("Transformer configuration is illegal.", e); + } catch (TransformerException e) { + throw new IllegalArgumentException("Transformer failed.", e); + } catch (IOException e) { + throw new IllegalArgumentException("Can't perform IO.", e); + } + } + + /** + * Changes the value describing by xpath to specific value. And updates file. + * + * @param file Which will read first and updated later. + * @param xpath Xpath to find specific Node. + * @param value Value to be set for found node. + */ + public static void updateXml(File file, String xpath, String value) { + XPath xp = XPATH_FACTORY.newXPath(); + + try { + XPathExpression root = xp.compile("/"); + Document document = (Document) root.evaluate(new InputSource(new FileInputStream(file)), XPathConstants.NODE); + XPathExpression nodeXpath = xp.compile(xpath); + Node node = (Node) nodeXpath.evaluate(document, XPathConstants.NODE); + node.setNodeValue(value); + writeXml(file, document); + } catch (XPathExpressionException e) { + throw new IllegalArgumentException("Illegal xpath.", e); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Can't find file.", e); + } + } + + /** + * @param file File to remove. + * @throws IOException If file not found or can't be removed. + */ + public static void removeFile(File file) throws IOException { + if (!file.exists()) { + throw new IOException("File not found " + file + '.'); + } + + if (!file.delete()) { + throw new IOException("Can't delete " + file + '.'); + } + } + + public static byte[] _getBytes(File file) throws IOException { + int size = Ints.checkedCast(file.length()); + InputStream stream = new FileInputStream(file); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(size); + byte[] chunk = new byte[1048576]; + while (true) { + int byteCount = stream.read(chunk); + if (byteCount < 0) { + break; + } else { + byteArrayOutputStream.write(chunk, 0, byteCount); + } + } + stream.close(); + return byteArrayOutputStream.toByteArray(); + } + + /** + * @param file File to be read. + * @return File content as a byte array. + * @throws IOException if can't read file. + * @throws FileNotFoundException if can't find file. + */ + public static byte[] getBytes(File file) throws IOException { + if (file.isFile()) { + long size = file.length(); + FileInputStream stream = new FileInputStream(file); + FileChannel channel = stream.getChannel(); + ByteBuffer bytes = ByteBuffer.allocate(Ints.checkedCast(size)); + channel.read(bytes); + channel.close(); + stream.close(); + return bytes.array(); + } else { + throw new FileNotFoundException("Can't find " + file + '.'); + } + } + + /** + * Returns first 255 bytes of the file. Returns smaller number of bytes it it contains less. + * + * @param file File to be read. + * @return File content as a byte array. + * @throws IOException if can't read file. + * @throws FileNotFoundException if can't find file. + */ + public static FirstBytes getFirstBytes(File file) throws IOException { + if (file.isFile()) { + boolean truncated = false; + long size = file.length(); + if (size > 255) { + truncated = true; + size = 255; + } + FileInputStream stream = new FileInputStream(file); + FileChannel channel = stream.getChannel(); + ByteBuffer bytes = ByteBuffer.allocate(Ints.checkedCast(size)); + channel.read(bytes); + channel.close(); + stream.close(); + return new FirstBytes(truncated, bytes.array()); + } else { + throw new FileNotFoundException("Can't find " + file + '.'); + } + } + + /** + * Accepts not fidden files. + */ + public static class NotHiddenFileFilter implements FilenameFilter { + @Override + public boolean accept(File dir, String name) { + return !new File(dir, name).isHidden(); + } + } + + /** + * @param file Any file. + * @return String Name part (simple name without extension). + */ + public static String getName(File file) { + String name = file.getName(); + if (name.contains(".")) { + return name.substring(0, name.lastIndexOf('.')); + } else { + return name; + } + } + + /** + * @param file Any file. + * @return String Extension with dot in lowercase. For example, ".cpp". + */ + public static String getExt(File file) { + String name = file.getName(); + if (name.contains(".")) { + return name.substring(name.lastIndexOf('.')).toLowerCase(); + } else { + return ""; + } + } + + /** + * @param directory Directory to be processed. + * @return Total length of _all_ nested files in the directory. + */ + public static long getDirectorySize(File directory) { + if (!directory.isDirectory()) { + throw new IllegalArgumentException("Abstract path " + directory + " is not a directory."); + } + + long result = 0; + + File[] files = directory.listFiles(); + + for (File file : files) { + if (file.isFile()) { + result += file.length(); + } + + if (file.isDirectory()) { + result += getDirectorySize(file); + } + } + + return result; + } + + /** + * @param servletContext ServletContext. + * @param path String. + * @return As servletContext.getRealPath(path) (tomcats 7 and 8 work differently). + */ + public static String getRealPath(ServletContext servletContext, String path) { + String result = servletContext.getRealPath(path); + if (result == null) { + if (path.startsWith("/")) { + result = servletContext.getRealPath(path.substring(1)); + } else { + result = servletContext.getRealPath('/' + path); + } + } + + if (result == null && new File(path).isAbsolute()) { + return path; + } + + return result; + } + + public static class FirstBytes { + private final boolean truncated; + private final byte[] bytes; + + private FirstBytes(boolean truncated, byte[] bytes) { + this.truncated = truncated; + this.bytes = bytes; + } + + public boolean isTruncated() { + return truncated; + } + + public byte[] getBytes() { + return bytes; + } + } +} diff --git a/code/src/main/java/org/nocturne/util/ReflectionUtil.java b/code/src/main/java/org/nocturne/util/ReflectionUtil.java index dd601f7..035998f 100644 --- a/code/src/main/java/org/nocturne/util/ReflectionUtil.java +++ b/code/src/main/java/org/nocturne/util/ReflectionUtil.java @@ -1,112 +1,112 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.util; - -import org.nocturne.exception.ReflectionException; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Reflection utilities. - * - * @author Mike Mirzayanov - */ -public class ReflectionUtil { - private static final Map, Class> originalClassByWrapperClass = new HashMap<>(); - private static final ReadWriteLock originalClassByWrapperClassMapLock = new ReentrantReadWriteLock(); - - /** - * Invokes method by name for object, finds method among methods - * of specified class. - * - * @param clazz Class instance where to find method. - * @param object Object which method will be invoked. - * @param methodName Method name. - * @param args Method arguments. - * @return Object Method return value. - * @throws ReflectionException If it can't invoke method. - */ - public static Object invoke(Class clazz, Object object, String methodName, Object... args) throws ReflectionException { - Method[] methods = clazz.getDeclaredMethods(); - - for (Method method : methods) { - if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) { - try { - method.setAccessible(true); - return method.invoke(object, args); - } catch (Exception e) { - throw new ReflectionException( - "Can't invoke method " + methodName + " of the class " + clazz.getName() + '.', e - ); - } - } - } - - if (clazz.getSuperclass() == null) { - throw new ReflectionException("Can't find method " + methodName + " of the class " + clazz.getName() + '.'); - } else { - return invoke(clazz.getSuperclass(), object, methodName, args); - } - } - - /** - * Invokes method by name. - * - * @param object Object which method will be invoked. - * @param methodName Method name. - * @param args Method arguments. - * @return Object Method return value. - * @throws ReflectionException If it can't invoke method. - */ - public static Object invoke(Object object, String methodName, Object... args) throws ReflectionException { - Class clazz = object.getClass(); - return invoke(clazz, object, methodName, args); - } - - /** - * Original class can be wrapped by Google Guice because of IoC. - * The method returns original class by possible wrapped. - * - * @param wrapperClass wrapper class or original class. - * @return original class. - */ - public static Class getOriginalClass(Class wrapperClass) { - Class originalClass; - - Lock readLock = originalClassByWrapperClassMapLock.readLock(); - readLock.lock(); - try { - originalClass = originalClassByWrapperClass.get(wrapperClass); - if (originalClass != null) { - return originalClass; - } - } finally { - readLock.unlock(); - } - - originalClass = wrapperClass; - while (isWrapperClass(originalClass)) { - originalClass = originalClass.getSuperclass(); - } - - Lock writeLock = originalClassByWrapperClassMapLock.writeLock(); - writeLock.lock(); - try { - originalClassByWrapperClass.put(wrapperClass, originalClass); - return originalClass; - } finally { - writeLock.unlock(); - } - } - - private static boolean isWrapperClass(Class clazz) { - String className = clazz.getName(); - return className.contains("$$") || className.contains("EnhancerByGuice"); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.util; + +import org.nocturne.exception.ReflectionException; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Reflection utilities. + * + * @author Mike Mirzayanov + */ +public class ReflectionUtil { + private static final Map, Class> originalClassByWrapperClass = new HashMap<>(); + private static final ReadWriteLock originalClassByWrapperClassMapLock = new ReentrantReadWriteLock(); + + /** + * Invokes method by name for object, finds method among methods + * of specified class. + * + * @param clazz Class instance where to find method. + * @param object Object which method will be invoked. + * @param methodName Method name. + * @param args Method arguments. + * @return Object Method return value. + * @throws ReflectionException If it can't invoke method. + */ + public static Object invoke(Class clazz, Object object, String methodName, Object... args) throws ReflectionException { + Method[] methods = clazz.getDeclaredMethods(); + + for (Method method : methods) { + if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) { + try { + method.setAccessible(true); + return method.invoke(object, args); + } catch (Exception e) { + throw new ReflectionException( + "Can't invoke method " + methodName + " of the class " + clazz.getName() + '.', e + ); + } + } + } + + if (clazz.getSuperclass() == null) { + throw new ReflectionException("Can't find method " + methodName + " of the class " + clazz.getName() + '.'); + } else { + return invoke(clazz.getSuperclass(), object, methodName, args); + } + } + + /** + * Invokes method by name. + * + * @param object Object which method will be invoked. + * @param methodName Method name. + * @param args Method arguments. + * @return Object Method return value. + * @throws ReflectionException If it can't invoke method. + */ + public static Object invoke(Object object, String methodName, Object... args) throws ReflectionException { + Class clazz = object.getClass(); + return invoke(clazz, object, methodName, args); + } + + /** + * Original class can be wrapped by Google Guice because of IoC. + * The method returns original class by possible wrapped. + * + * @param wrapperClass wrapper class or original class. + * @return original class. + */ + public static Class getOriginalClass(Class wrapperClass) { + Class originalClass; + + Lock readLock = originalClassByWrapperClassMapLock.readLock(); + readLock.lock(); + try { + originalClass = originalClassByWrapperClass.get(wrapperClass); + if (originalClass != null) { + return originalClass; + } + } finally { + readLock.unlock(); + } + + originalClass = wrapperClass; + while (isWrapperClass(originalClass)) { + originalClass = originalClass.getSuperclass(); + } + + Lock writeLock = originalClassByWrapperClassMapLock.writeLock(); + writeLock.lock(); + try { + originalClassByWrapperClass.put(wrapperClass, originalClass); + return originalClass; + } finally { + writeLock.unlock(); + } + } + + private static boolean isWrapperClass(Class clazz) { + String className = clazz.getName(); + return className.contains("$$") || className.contains("EnhancerByGuice"); + } +} diff --git a/code/src/main/java/org/nocturne/util/RequestUtil.java b/code/src/main/java/org/nocturne/util/RequestUtil.java index c3192e2..b047cec 100644 --- a/code/src/main/java/org/nocturne/util/RequestUtil.java +++ b/code/src/main/java/org/nocturne/util/RequestUtil.java @@ -1,302 +1,302 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.util; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileItemFactory; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.nocturne.collection.SingleEntryList; -import org.nocturne.exception.NocturneException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.regex.Pattern; - -/** - * Request utilities. - * - * @author Mike Mirzayanov - */ -public class RequestUtil { - private static final String GET_REQUEST_PARAMS_CACHED_RESULT = "Codeforces::getRequestParamsCachedResult"; - private static final Pattern QUERY_STRING_SPLIT_PATTERN = Pattern.compile("&"); - - public static String getRequestUriAndQueryString(HttpServletRequest request) { - String result = request.getRequestURI(); - if (StringUtil.isNotEmpty(request.getQueryString())) { - result += '?' + request.getQueryString(); - } - return result; - } - - public static Map> getRequestParams(HttpServletRequest request) { - Map> cachedRequestParameters = getCachedRequestParameters(request); - if (cachedRequestParameters != null) { - return cachedRequestParameters; - } - - if ("POST".equalsIgnoreCase(request.getMethod())) { - addUploadedItemsToRequestAttributes(request); - } - - Map> requestParameters = new HashMap<>(); - - addRequestParametersFromParameterMap(requestParameters, request); - addRequestParametersFromAttributes(requestParameters, request); - - setCachedRequestParameters(request, requestParameters); - - return requestParameters; - } - - @SuppressWarnings("unchecked") - private static Map> getCachedRequestParameters(HttpServletRequest request) { - Object cachedRequestParameters = request.getAttribute(GET_REQUEST_PARAMS_CACHED_RESULT); - if (cachedRequestParameters instanceof Map) { - return (Map>) cachedRequestParameters; - } else { - return null; - } - } - - private static void setCachedRequestParameters( - HttpServletRequest request, Map> requestParameters) { - request.setAttribute(GET_REQUEST_PARAMS_CACHED_RESULT, requestParameters); - } - - private static void addUploadedItemsToRequestAttributes(HttpServletRequest request) { - try { - FileItemFactory factory = new DiskFileItemFactory(); - ServletFileUpload upload = new ServletFileUpload(factory); - List items = upload.parseRequest(request); - - Map> fileNamesByFieldName = new HashMap<>(); - Map> fileBytesByFieldName = new HashMap<>(); - List itemNames = new ArrayList<>(items.size()); - - for (FileItem item : items) { - String name = item.getFieldName(); - itemNames.add(name); - InputStream inputStream = item.getInputStream(); - byte[] bytes = StreamUtil.getAsByteArray(inputStream); - if (bytes != null) { - if (item.isFormField()) { - Object existingValue = request.getAttribute(name); - String value = new String(bytes, StandardCharsets.UTF_8); - - if (existingValue == null) { - request.setAttribute(name, value); - } else if (existingValue instanceof Collection) { - addStringToRawCollection((Collection) existingValue, value); - } else { - Collection values = new ArrayList<>(4); - values.add(existingValue); - values.add(value); - request.setAttribute(name, values); - } - } else { - request.setAttribute(name, bytes); - - if (!fileBytesByFieldName.containsKey(name)) { - fileBytesByFieldName.put(name, new ArrayList<>(1)); - } - fileBytesByFieldName.get(name).add(bytes); - } - } - inputStream.close(); - - if (item.getName() != null && !item.getName().isEmpty()) { - request.setAttribute(name + "::name", item.getName()); - - if (!fileNamesByFieldName.containsKey(name)) { - fileNamesByFieldName.put(name, new ArrayList<>(1)); - } - fileNamesByFieldName.get(name).add(item.getName()); - } - } - - - for (Map.Entry> e : fileNamesByFieldName.entrySet()) { - String fieldName = e.getKey(); - - String[] fileNames = new String[e.getValue().size()]; - e.getValue().toArray(fileNames); - request.setAttribute(fieldName + "::name[]", fileNames); - - byte[][] fileBytes = new byte[e.getValue().size()][]; - fileBytesByFieldName.get(fieldName).toArray(fileBytes); - request.setAttribute(fieldName + "[]", fileBytes); - } - - request.setAttribute("nocturne.uploaded-item-names", itemNames); - } catch (Exception ignored) { - // No operations. - } - } - - private static void addRequestParametersFromParameterMap( - Map> requestParameters, HttpServletRequest request) { - Map parameterMap = request.getParameterMap(); - - for (Map.Entry e : parameterMap.entrySet()) { - String name = e.getKey().toString(); - Object value = e.getValue(); - - List parameters; - - if (value.getClass().isArray()) { - Object[] values = (Object[]) value; - int count = values.length; - - parameters = count <= 1 ? new SingleEntryList<>() : new ArrayList<>(count); - - for (int index = 0; index < count; ++index) { - parameters.add(values[index].toString()); - } - } else { - parameters = new SingleEntryList<>(value.toString()); - } - - requestParameters.put(name, parameters); - - if (name.endsWith("[]")) { - name = name.substring(0, name.length() - "[]".length()); - requestParameters.put(name, parameters); - } - } - } - - private static void addRequestParametersFromAttributes( - Map> requestParameters, HttpServletRequest request) { - Enumeration enumeration = request.getAttributeNames(); - - while (enumeration.hasMoreElements()) { - String name = enumeration.nextElement().toString(); - Object value = request.getAttribute(name); - - if (value != null) { - if (value instanceof byte[]) { - requestParameters.put(name, new SingleEntryList<>(new String((byte[]) value, StandardCharsets.UTF_8))); - continue; - } - - if (value instanceof List) { - List list = (List) value; - if (isListOfStrings(list)) { - requestParameters.put(name, castToStringList(list)); - continue; - } - } - - if (value instanceof Collection) { - List list = new ArrayList<>((Collection) value); - if (isListOfStrings(list)) { - requestParameters.put(name, castToStringList(list)); - continue; - } - } - - requestParameters.put(name, new SingleEntryList<>(request.getAttribute(name).toString())); - } - } - } - - /** - * @param httpServletRequest Http request. - * @return Remote address taking in account X-Real-IP. - */ - @Nonnull - public static String getRemoteAddr(@Nonnull HttpServletRequest httpServletRequest) { - String ip = StringUtil.trimToNull(httpServletRequest.getHeader("X-Real-IP")); - if (ip != null) { - return ip; - } - - ip = StringUtil.trimToNull(httpServletRequest.getRemoteAddr()); - if (ip != null) { - return ip; - } - - return ""; - } - - private static boolean isListOfStrings(List list) { - for (Object o : list) { - if (!(o instanceof String)) { - return false; - } - } - - return true; - } - - @SuppressWarnings("unchecked") - private static List castToStringList(List list) { - return (List) list; - } - - @SuppressWarnings("unchecked") - private static void addStringToRawCollection(Collection existingValue, String value) { - existingValue.add(value); - } - - /** - * Parses query part from URL and extracts params. - * - * @param request Http request. - * @return Map containing all parameters as strings. Expects UTF-8 encoding. - */ - public static Map parseGetParameters(HttpServletRequest request) { - Map parameters = new HashMap<>(); - try { - String query = request.getQueryString(); - if (query != null && !query.isEmpty()) { - String[] tokens = QUERY_STRING_SPLIT_PATTERN.split(query); - for (String token : tokens) { - if (!token.isEmpty()) { - int index = token.indexOf('='); - - String key; - String value; - - if (index >= 0) { - key = token.substring(0, index); - value = token.substring(index + 1); - } else { - key = token; - value = ""; - } - - try { - parameters.put(key, URLDecoder.decode(value, StandardCharsets.UTF_8.name())); - } catch (IllegalArgumentException ignored) { - // No operations. - } - } - } - } - } catch (UnsupportedEncodingException e) { - throw new NocturneException("Can't decode parameter because of illegal encoding.", e); - } - - return parameters; - } - - public static String getFirst( - @Nonnull Map> parameterValuesByName, @Nonnull String parameterName) { - List parameterValues = parameterValuesByName.get(parameterName); - return parameterValues == null || parameterValues.isEmpty() ? null : parameterValues.get(0); - } - - public static String getFirst(@Nullable List parameterValues) { - return parameterValues == null || parameterValues.isEmpty() ? null : parameterValues.get(0); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.util; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.nocturne.collection.SingleEntryList; +import org.nocturne.exception.NocturneException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Pattern; + +/** + * Request utilities. + * + * @author Mike Mirzayanov + */ +public class RequestUtil { + private static final String GET_REQUEST_PARAMS_CACHED_RESULT = "Codeforces::getRequestParamsCachedResult"; + private static final Pattern QUERY_STRING_SPLIT_PATTERN = Pattern.compile("&"); + + public static String getRequestUriAndQueryString(HttpServletRequest request) { + String result = request.getRequestURI(); + if (StringUtil.isNotEmpty(request.getQueryString())) { + result += '?' + request.getQueryString(); + } + return result; + } + + public static Map> getRequestParams(HttpServletRequest request) { + Map> cachedRequestParameters = getCachedRequestParameters(request); + if (cachedRequestParameters != null) { + return cachedRequestParameters; + } + + if ("POST".equalsIgnoreCase(request.getMethod())) { + addUploadedItemsToRequestAttributes(request); + } + + Map> requestParameters = new HashMap<>(); + + addRequestParametersFromParameterMap(requestParameters, request); + addRequestParametersFromAttributes(requestParameters, request); + + setCachedRequestParameters(request, requestParameters); + + return requestParameters; + } + + @SuppressWarnings("unchecked") + private static Map> getCachedRequestParameters(HttpServletRequest request) { + Object cachedRequestParameters = request.getAttribute(GET_REQUEST_PARAMS_CACHED_RESULT); + if (cachedRequestParameters instanceof Map) { + return (Map>) cachedRequestParameters; + } else { + return null; + } + } + + private static void setCachedRequestParameters( + HttpServletRequest request, Map> requestParameters) { + request.setAttribute(GET_REQUEST_PARAMS_CACHED_RESULT, requestParameters); + } + + private static void addUploadedItemsToRequestAttributes(HttpServletRequest request) { + try { + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + List items = upload.parseRequest(request); + + Map> fileNamesByFieldName = new HashMap<>(); + Map> fileBytesByFieldName = new HashMap<>(); + List itemNames = new ArrayList<>(items.size()); + + for (FileItem item : items) { + String name = item.getFieldName(); + itemNames.add(name); + InputStream inputStream = item.getInputStream(); + byte[] bytes = StreamUtil.getAsByteArray(inputStream); + if (bytes != null) { + if (item.isFormField()) { + Object existingValue = request.getAttribute(name); + String value = new String(bytes, StandardCharsets.UTF_8); + + if (existingValue == null) { + request.setAttribute(name, value); + } else if (existingValue instanceof Collection) { + addStringToRawCollection((Collection) existingValue, value); + } else { + Collection values = new ArrayList<>(4); + values.add(existingValue); + values.add(value); + request.setAttribute(name, values); + } + } else { + request.setAttribute(name, bytes); + + if (!fileBytesByFieldName.containsKey(name)) { + fileBytesByFieldName.put(name, new ArrayList<>(1)); + } + fileBytesByFieldName.get(name).add(bytes); + } + } + inputStream.close(); + + if (item.getName() != null && !item.getName().isEmpty()) { + request.setAttribute(name + "::name", item.getName()); + + if (!fileNamesByFieldName.containsKey(name)) { + fileNamesByFieldName.put(name, new ArrayList<>(1)); + } + fileNamesByFieldName.get(name).add(item.getName()); + } + } + + + for (Map.Entry> e : fileNamesByFieldName.entrySet()) { + String fieldName = e.getKey(); + + String[] fileNames = new String[e.getValue().size()]; + e.getValue().toArray(fileNames); + request.setAttribute(fieldName + "::name[]", fileNames); + + byte[][] fileBytes = new byte[e.getValue().size()][]; + fileBytesByFieldName.get(fieldName).toArray(fileBytes); + request.setAttribute(fieldName + "[]", fileBytes); + } + + request.setAttribute("nocturne.uploaded-item-names", itemNames); + } catch (Exception ignored) { + // No operations. + } + } + + private static void addRequestParametersFromParameterMap( + Map> requestParameters, HttpServletRequest request) { + Map parameterMap = request.getParameterMap(); + + for (Map.Entry e : parameterMap.entrySet()) { + String name = e.getKey().toString(); + Object value = e.getValue(); + + List parameters; + + if (value.getClass().isArray()) { + Object[] values = (Object[]) value; + int count = values.length; + + parameters = count <= 1 ? new SingleEntryList<>() : new ArrayList<>(count); + + for (int index = 0; index < count; ++index) { + parameters.add(values[index].toString()); + } + } else { + parameters = new SingleEntryList<>(value.toString()); + } + + requestParameters.put(name, parameters); + + if (name.endsWith("[]")) { + name = name.substring(0, name.length() - "[]".length()); + requestParameters.put(name, parameters); + } + } + } + + private static void addRequestParametersFromAttributes( + Map> requestParameters, HttpServletRequest request) { + Enumeration enumeration = request.getAttributeNames(); + + while (enumeration.hasMoreElements()) { + String name = enumeration.nextElement().toString(); + Object value = request.getAttribute(name); + + if (value != null) { + if (value instanceof byte[]) { + requestParameters.put(name, new SingleEntryList<>(new String((byte[]) value, StandardCharsets.UTF_8))); + continue; + } + + if (value instanceof List) { + List list = (List) value; + if (isListOfStrings(list)) { + requestParameters.put(name, castToStringList(list)); + continue; + } + } + + if (value instanceof Collection) { + List list = new ArrayList<>((Collection) value); + if (isListOfStrings(list)) { + requestParameters.put(name, castToStringList(list)); + continue; + } + } + + requestParameters.put(name, new SingleEntryList<>(request.getAttribute(name).toString())); + } + } + } + + /** + * @param httpServletRequest Http request. + * @return Remote address taking in account X-Real-IP. + */ + @Nonnull + public static String getRemoteAddr(@Nonnull HttpServletRequest httpServletRequest) { + String ip = StringUtil.trimToNull(httpServletRequest.getHeader("X-Real-IP")); + if (ip != null) { + return ip; + } + + ip = StringUtil.trimToNull(httpServletRequest.getRemoteAddr()); + if (ip != null) { + return ip; + } + + return ""; + } + + private static boolean isListOfStrings(List list) { + for (Object o : list) { + if (!(o instanceof String)) { + return false; + } + } + + return true; + } + + @SuppressWarnings("unchecked") + private static List castToStringList(List list) { + return (List) list; + } + + @SuppressWarnings("unchecked") + private static void addStringToRawCollection(Collection existingValue, String value) { + existingValue.add(value); + } + + /** + * Parses query part from URL and extracts params. + * + * @param request Http request. + * @return Map containing all parameters as strings. Expects UTF-8 encoding. + */ + public static Map parseGetParameters(HttpServletRequest request) { + Map parameters = new HashMap<>(); + try { + String query = request.getQueryString(); + if (query != null && !query.isEmpty()) { + String[] tokens = QUERY_STRING_SPLIT_PATTERN.split(query); + for (String token : tokens) { + if (!token.isEmpty()) { + int index = token.indexOf('='); + + String key; + String value; + + if (index >= 0) { + key = token.substring(0, index); + value = token.substring(index + 1); + } else { + key = token; + value = ""; + } + + try { + parameters.put(key, URLDecoder.decode(value, StandardCharsets.UTF_8.name())); + } catch (IllegalArgumentException ignored) { + // No operations. + } + } + } + } + } catch (UnsupportedEncodingException e) { + throw new NocturneException("Can't decode parameter because of illegal encoding.", e); + } + + return parameters; + } + + public static String getFirst( + @Nonnull Map> parameterValuesByName, @Nonnull String parameterName) { + List parameterValues = parameterValuesByName.get(parameterName); + return parameterValues == null || parameterValues.isEmpty() ? null : parameterValues.get(0); + } + + public static String getFirst(@Nullable List parameterValues) { + return parameterValues == null || parameterValues.isEmpty() ? null : parameterValues.get(0); + } +} diff --git a/code/src/main/java/org/nocturne/util/StreamUtil.java b/code/src/main/java/org/nocturne/util/StreamUtil.java index 9691b53..4f0e3a3 100644 --- a/code/src/main/java/org/nocturne/util/StreamUtil.java +++ b/code/src/main/java/org/nocturne/util/StreamUtil.java @@ -1,90 +1,90 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Stream utilities. - * - * @author Mike Mirzayanov - */ -public class StreamUtil { - private static final int CHUNK_SIZE = 262144; - - /** - * Reads stream to byte array. Closes the stream on exit. - * - * @param inputStream InputStream instance to be read. - * @return InputStream instance content as byte[]. - * @throws IOException On IO error. - */ - public static byte[] getAsByteArray(InputStream inputStream) throws IOException { - return getAsByteArray(inputStream, Integer.MAX_VALUE); - } - - /** - * Reads stream to byte array. - * - * @param inputStream InputStream instance to be read. - * @param maxSize Maximal possible content size. Throws IOException if size exceeded. - * @return InputStream instance content as byte[]. - * @throws IOException On IO error. Throws IOException if maxSize exceeded. - */ - public static byte[] getAsByteArray(InputStream inputStream, int maxSize) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - byte[] chunk = new byte[CHUNK_SIZE]; - while (true) { - int size = inputStream.read(chunk); - if (size == -1) { - break; - } - outputStream.write(chunk, 0, size); - if (outputStream.size() > maxSize) { - throw new IOException("Data exceeds " + maxSize + " bytes"); - } - } - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // No operation. - } - } - return outputStream.toByteArray(); - } - - /** - * Transfers the data from one stream to another. Closes streams on exit. - * - * @param in Source stream. - * @param out Destination stream. - * @throws IOException On IO errors. - */ - public static void copyInputStream(InputStream in, OutputStream out) throws IOException { - try { - byte[] buffer = new byte[CHUNK_SIZE]; - int len; - - while ((len = in.read(buffer)) >= 0) { - out.write(buffer, 0, len); - } - } finally { - try { - in.close(); - } catch (IOException e) { - // No operations. - } - try { - out.close(); - } catch (IOException e) { - // No operations. - } - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Stream utilities. + * + * @author Mike Mirzayanov + */ +public class StreamUtil { + private static final int CHUNK_SIZE = 262144; + + /** + * Reads stream to byte array. Closes the stream on exit. + * + * @param inputStream InputStream instance to be read. + * @return InputStream instance content as byte[]. + * @throws IOException On IO error. + */ + public static byte[] getAsByteArray(InputStream inputStream) throws IOException { + return getAsByteArray(inputStream, Integer.MAX_VALUE); + } + + /** + * Reads stream to byte array. + * + * @param inputStream InputStream instance to be read. + * @param maxSize Maximal possible content size. Throws IOException if size exceeded. + * @return InputStream instance content as byte[]. + * @throws IOException On IO error. Throws IOException if maxSize exceeded. + */ + public static byte[] getAsByteArray(InputStream inputStream, int maxSize) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + byte[] chunk = new byte[CHUNK_SIZE]; + while (true) { + int size = inputStream.read(chunk); + if (size == -1) { + break; + } + outputStream.write(chunk, 0, size); + if (outputStream.size() > maxSize) { + throw new IOException("Data exceeds " + maxSize + " bytes"); + } + } + } finally { + try { + inputStream.close(); + } catch (IOException e) { + // No operation. + } + } + return outputStream.toByteArray(); + } + + /** + * Transfers the data from one stream to another. Closes streams on exit. + * + * @param in Source stream. + * @param out Destination stream. + * @throws IOException On IO errors. + */ + public static void copyInputStream(InputStream in, OutputStream out) throws IOException { + try { + byte[] buffer = new byte[CHUNK_SIZE]; + int len; + + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + } finally { + try { + in.close(); + } catch (IOException e) { + // No operations. + } + try { + out.close(); + } catch (IOException e) { + // No operations. + } + } + } +} diff --git a/code/src/main/java/org/nocturne/util/StringUtil.java b/code/src/main/java/org/nocturne/util/StringUtil.java index 305b83b..517ce98 100644 --- a/code/src/main/java/org/nocturne/util/StringUtil.java +++ b/code/src/main/java/org/nocturne/util/StringUtil.java @@ -1,280 +1,280 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.regex.Pattern; - -/** - * Some string utilities. You can use apache StringUtils instead of it. - * - * @author Mike Mirzayanov - * @author Maxim Shipko (sladethe@gmail.com) - */ -public class StringUtil { - /** - * Dangerous characters for HTML. - */ - private static final String DANGEROUS_CHARS = "&\"\'<>"; - - static final char NON_BREAKING_SPACE = (char) 160; - static final char THIN_SPACE = '\u2009'; - static final char ZERO_WIDTH_SPACE = '\u200B'; - - public static boolean isWhitespace(char c) { - return Character.isWhitespace(c) || c == NON_BREAKING_SPACE || c == ZERO_WIDTH_SPACE; - } - - /** - * @param s String. - * @return {@code true} iff {@code s} is {@code null} or empty. - */ - public static boolean isEmpty(@Nullable String s) { - return s == null || s.isEmpty(); - } - - /** - * @param s String. - * @return {@code true} iff {@code s} is not {@code null} and not empty. - */ - public static boolean isNotEmpty(@Nullable String s) { - return s != null && !s.isEmpty(); - } - - /** - * @param s String. - * @return {@code true} iff {@code s} is {@code null}, empty or contains only whitespaces. - * @see #isWhitespace(char) - */ - public static boolean isBlank(@Nullable String s) { - if (s == null || s.isEmpty()) { - return true; - } - - for (int charIndex = s.length() - 1; charIndex >= 0; --charIndex) { - if (!isWhitespace(s.charAt(charIndex))) { - return false; - } - } - - return true; - } - - /** - * @param s String. - * @return {@code true} iff {@code s} is not {@code null}, not empty - * and contains at least one character that is not whitespace. - * @see #isWhitespace(char) - */ - public static boolean isNotBlank(@Nullable String s) { - if (s == null || s.isEmpty()) { - return false; - } - - for (int charIndex = s.length() - 1; charIndex >= 0; --charIndex) { - if (!isWhitespace(s.charAt(charIndex))) { - return true; - } - } - - return false; - } - - /** - * Compares two strings case-sensitive. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@code null} - * or the string B represents a {@code String} equivalent to the string A - */ - public static boolean equals(@Nullable String stringA, @Nullable String stringB) { - return stringA == null ? stringB == null : stringA.equals(stringB); - } - - /** - * Compares two strings case-sensitive. {@code null} and empty values considered equals. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@link #isEmpty(String) empty} - * or the string B represents a {@code String} equal to the string A - */ - public static boolean equalsOrEmpty(@Nullable String stringA, @Nullable String stringB) { - return isEmpty(stringA) ? isEmpty(stringB) : stringA.equals(stringB); - } - - /** - * Compares two strings case-sensitive. {@code null}, empty and blank values considered equals. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@link #isBlank(String) blank} - * or the string B represents a {@code String} equal to the string A - */ - public static boolean equalsOrBlank(@Nullable String stringA, @Nullable String stringB) { - return isBlank(stringA) ? isBlank(stringB) : stringA.equals(stringB); - } - - /** - * Compares two strings case-insensitive. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@code null} - * or the string B represents a {@code String} equal to the string A - */ - public static boolean equalsIgnoreCase(@Nullable String stringA, @Nullable String stringB) { - return stringA == null ? stringB == null : stringA.equalsIgnoreCase(stringB); - } - - /** - * Compares two strings case-insensitive. {@code null} and empty values considered equals. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@link #isEmpty(String) empty} - * or the string B represents a {@code String} equal to the string A - */ - public static boolean equalsOrEmptyIgnoreCase(@Nullable String stringA, @Nullable String stringB) { - return isEmpty(stringA) ? isEmpty(stringB) : stringA.equalsIgnoreCase(stringB); - } - - /** - * Compares two strings case-insensitive. {@code null}, empty and blank values considered equals. - * - * @param stringA first string - * @param stringB second string - * @return {@code true} iff both strings A and B are {@link #isBlank(String) blank} - * or the string B represents a {@code String} equal to the string A - */ - public static boolean equalsOrBlankIgnoreCase(@Nullable String stringA, @Nullable String stringB) { - return isBlank(stringA) ? isBlank(stringB) : stringA.equalsIgnoreCase(stringB); - } - - public static int length(@Nullable String s) { - return s == null ? 0 : s.length(); - } - - @Nonnull - public static String nullToEmpty(@Nullable String s) { - return s == null ? "" : s; - } - - @Nullable - public static String emptyToNull(@Nullable String s) { - return s == null || s.isEmpty() ? null : s; - } - - @Nullable - public static String trim(@Nullable String s) { - if (s == null) { - return null; - } - - int lastIndex = s.length() - 1; - int beginIndex = 0; - int endIndex = lastIndex; - - while (beginIndex <= lastIndex && isWhitespace(s.charAt(beginIndex))) { - ++beginIndex; - } - - while (endIndex > beginIndex && isWhitespace(s.charAt(endIndex))) { - --endIndex; - } - - return beginIndex == 0 && endIndex == lastIndex ? s : s.substring(beginIndex, endIndex + 1); - } - - @Nullable - public static String trimToNull(@Nullable String s) { - return s == null ? null : (s = trim(s)).isEmpty() ? null : s; - } - - @Nonnull - public static String trimToEmpty(@Nullable String s) { - return s == null ? "" : trim(s); - } - - @Nullable - public static String trimRight(@Nullable String s) { - if (s == null) { - return null; - } - - int lastIndex = s.length() - 1; - int endIndex = lastIndex; - - while (endIndex >= 0 && isWhitespace(s.charAt(endIndex))) { - --endIndex; - } - - return endIndex == lastIndex ? s : s.substring(0, endIndex + 1); - } - - @Nullable - public static String trimLeft(@Nullable String s) { - if (s == null) { - return null; - } - - int lastIndex = s.length() - 1; - int beginIndex = 0; - - while (beginIndex <= lastIndex && isWhitespace(s.charAt(beginIndex))) { - ++beginIndex; - } - - return beginIndex == 0 ? s : s.substring(beginIndex, lastIndex + 1); - } - - /** - * @param s String to be checked for HTML-dangerous characters. - * @return Is given string contain at least one HTML-dangerous character (which should be encoded in HTML)? - */ - public static boolean containsDangerousCharacters(String s) { - for (int i = 0; i < s.length(); ++i) { - if (DANGEROUS_CHARS.indexOf(s.charAt(i)) >= 0) { - return true; - } - } - return false; - } - - /** - * @param c Character to be checked. - * @return Is character should be encoded for HTNL? - */ - public static boolean isDangerousCharacter(char c) { - return DANGEROUS_CHARS.indexOf(c) >= 0; - } - - public static class Patterns { - private Patterns() { - throw new UnsupportedOperationException(); - } - - public static final Pattern PLUS_PATTERN = Pattern.compile("\\+"); - public static final Pattern MINUS_PATTERN = Pattern.compile("\\-"); - public static final Pattern EQ_PATTERN = Pattern.compile("="); - public static final Pattern LT_PATTERN = Pattern.compile("<"); - public static final Pattern GT_PATTERN = Pattern.compile(">"); - public static final Pattern SPACE_PATTERN = Pattern.compile(" "); - public static final Pattern NBSP_PATTERN = Pattern.compile("" + (char) 160); - public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - public static final Pattern THIN_SPACE_PATTERN = Pattern.compile("" + '\u2009'); - public static final Pattern ZERO_WIDTH_SPACE_PATTERN = Pattern.compile("" + '\u200B'); - public static final Pattern TAB_PATTERN = Pattern.compile("\t"); - public static final Pattern CR_LF_PATTERN = Pattern.compile("\r\n"); - public static final Pattern CR_PATTERN = Pattern.compile("\r"); - public static final Pattern LF_PATTERN = Pattern.compile("\n"); - public static final Pattern SLASH_PATTERN = Pattern.compile("/"); - public static final Pattern COMMA_PATTERN = Pattern.compile(","); - public static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); - public static final Pattern COLON_PATTERN = Pattern.compile(":"); - public static final Pattern AMP_PATTERN = Pattern.compile("&"); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.util; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.regex.Pattern; + +/** + * Some string utilities. You can use apache StringUtils instead of it. + * + * @author Mike Mirzayanov + * @author Maxim Shipko (sladethe@gmail.com) + */ +public class StringUtil { + /** + * Dangerous characters for HTML. + */ + private static final String DANGEROUS_CHARS = "&\"\'<>"; + + static final char NON_BREAKING_SPACE = (char) 160; + static final char THIN_SPACE = '\u2009'; + static final char ZERO_WIDTH_SPACE = '\u200B'; + + public static boolean isWhitespace(char c) { + return Character.isWhitespace(c) || c == NON_BREAKING_SPACE || c == ZERO_WIDTH_SPACE; + } + + /** + * @param s String. + * @return {@code true} iff {@code s} is {@code null} or empty. + */ + public static boolean isEmpty(@Nullable String s) { + return s == null || s.isEmpty(); + } + + /** + * @param s String. + * @return {@code true} iff {@code s} is not {@code null} and not empty. + */ + public static boolean isNotEmpty(@Nullable String s) { + return s != null && !s.isEmpty(); + } + + /** + * @param s String. + * @return {@code true} iff {@code s} is {@code null}, empty or contains only whitespaces. + * @see #isWhitespace(char) + */ + public static boolean isBlank(@Nullable String s) { + if (s == null || s.isEmpty()) { + return true; + } + + for (int charIndex = s.length() - 1; charIndex >= 0; --charIndex) { + if (!isWhitespace(s.charAt(charIndex))) { + return false; + } + } + + return true; + } + + /** + * @param s String. + * @return {@code true} iff {@code s} is not {@code null}, not empty + * and contains at least one character that is not whitespace. + * @see #isWhitespace(char) + */ + public static boolean isNotBlank(@Nullable String s) { + if (s == null || s.isEmpty()) { + return false; + } + + for (int charIndex = s.length() - 1; charIndex >= 0; --charIndex) { + if (!isWhitespace(s.charAt(charIndex))) { + return true; + } + } + + return false; + } + + /** + * Compares two strings case-sensitive. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@code null} + * or the string B represents a {@code String} equivalent to the string A + */ + public static boolean equals(@Nullable String stringA, @Nullable String stringB) { + return stringA == null ? stringB == null : stringA.equals(stringB); + } + + /** + * Compares two strings case-sensitive. {@code null} and empty values considered equals. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@link #isEmpty(String) empty} + * or the string B represents a {@code String} equal to the string A + */ + public static boolean equalsOrEmpty(@Nullable String stringA, @Nullable String stringB) { + return isEmpty(stringA) ? isEmpty(stringB) : stringA.equals(stringB); + } + + /** + * Compares two strings case-sensitive. {@code null}, empty and blank values considered equals. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@link #isBlank(String) blank} + * or the string B represents a {@code String} equal to the string A + */ + public static boolean equalsOrBlank(@Nullable String stringA, @Nullable String stringB) { + return isBlank(stringA) ? isBlank(stringB) : stringA.equals(stringB); + } + + /** + * Compares two strings case-insensitive. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@code null} + * or the string B represents a {@code String} equal to the string A + */ + public static boolean equalsIgnoreCase(@Nullable String stringA, @Nullable String stringB) { + return stringA == null ? stringB == null : stringA.equalsIgnoreCase(stringB); + } + + /** + * Compares two strings case-insensitive. {@code null} and empty values considered equals. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@link #isEmpty(String) empty} + * or the string B represents a {@code String} equal to the string A + */ + public static boolean equalsOrEmptyIgnoreCase(@Nullable String stringA, @Nullable String stringB) { + return isEmpty(stringA) ? isEmpty(stringB) : stringA.equalsIgnoreCase(stringB); + } + + /** + * Compares two strings case-insensitive. {@code null}, empty and blank values considered equals. + * + * @param stringA first string + * @param stringB second string + * @return {@code true} iff both strings A and B are {@link #isBlank(String) blank} + * or the string B represents a {@code String} equal to the string A + */ + public static boolean equalsOrBlankIgnoreCase(@Nullable String stringA, @Nullable String stringB) { + return isBlank(stringA) ? isBlank(stringB) : stringA.equalsIgnoreCase(stringB); + } + + public static int length(@Nullable String s) { + return s == null ? 0 : s.length(); + } + + @Nonnull + public static String nullToEmpty(@Nullable String s) { + return s == null ? "" : s; + } + + @Nullable + public static String emptyToNull(@Nullable String s) { + return s == null || s.isEmpty() ? null : s; + } + + @Nullable + public static String trim(@Nullable String s) { + if (s == null) { + return null; + } + + int lastIndex = s.length() - 1; + int beginIndex = 0; + int endIndex = lastIndex; + + while (beginIndex <= lastIndex && isWhitespace(s.charAt(beginIndex))) { + ++beginIndex; + } + + while (endIndex > beginIndex && isWhitespace(s.charAt(endIndex))) { + --endIndex; + } + + return beginIndex == 0 && endIndex == lastIndex ? s : s.substring(beginIndex, endIndex + 1); + } + + @Nullable + public static String trimToNull(@Nullable String s) { + return s == null ? null : (s = trim(s)).isEmpty() ? null : s; + } + + @Nonnull + public static String trimToEmpty(@Nullable String s) { + return s == null ? "" : trim(s); + } + + @Nullable + public static String trimRight(@Nullable String s) { + if (s == null) { + return null; + } + + int lastIndex = s.length() - 1; + int endIndex = lastIndex; + + while (endIndex >= 0 && isWhitespace(s.charAt(endIndex))) { + --endIndex; + } + + return endIndex == lastIndex ? s : s.substring(0, endIndex + 1); + } + + @Nullable + public static String trimLeft(@Nullable String s) { + if (s == null) { + return null; + } + + int lastIndex = s.length() - 1; + int beginIndex = 0; + + while (beginIndex <= lastIndex && isWhitespace(s.charAt(beginIndex))) { + ++beginIndex; + } + + return beginIndex == 0 ? s : s.substring(beginIndex, lastIndex + 1); + } + + /** + * @param s String to be checked for HTML-dangerous characters. + * @return Is given string contain at least one HTML-dangerous character (which should be encoded in HTML)? + */ + public static boolean containsDangerousCharacters(String s) { + for (int i = 0; i < s.length(); ++i) { + if (DANGEROUS_CHARS.indexOf(s.charAt(i)) >= 0) { + return true; + } + } + return false; + } + + /** + * @param c Character to be checked. + * @return Is character should be encoded for HTNL? + */ + public static boolean isDangerousCharacter(char c) { + return DANGEROUS_CHARS.indexOf(c) >= 0; + } + + public static class Patterns { + private Patterns() { + throw new UnsupportedOperationException(); + } + + public static final Pattern PLUS_PATTERN = Pattern.compile("\\+"); + public static final Pattern MINUS_PATTERN = Pattern.compile("\\-"); + public static final Pattern EQ_PATTERN = Pattern.compile("="); + public static final Pattern LT_PATTERN = Pattern.compile("<"); + public static final Pattern GT_PATTERN = Pattern.compile(">"); + public static final Pattern SPACE_PATTERN = Pattern.compile(" "); + public static final Pattern NBSP_PATTERN = Pattern.compile("" + (char) 160); + public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + public static final Pattern THIN_SPACE_PATTERN = Pattern.compile("" + '\u2009'); + public static final Pattern ZERO_WIDTH_SPACE_PATTERN = Pattern.compile("" + '\u200B'); + public static final Pattern TAB_PATTERN = Pattern.compile("\t"); + public static final Pattern CR_LF_PATTERN = Pattern.compile("\r\n"); + public static final Pattern CR_PATTERN = Pattern.compile("\r"); + public static final Pattern LF_PATTERN = Pattern.compile("\n"); + public static final Pattern SLASH_PATTERN = Pattern.compile("/"); + public static final Pattern COMMA_PATTERN = Pattern.compile(","); + public static final Pattern SEMICOLON_PATTERN = Pattern.compile(";"); + public static final Pattern COLON_PATTERN = Pattern.compile(":"); + public static final Pattern AMP_PATTERN = Pattern.compile("&"); + } +} diff --git a/code/src/main/java/org/nocturne/validation/BytesLengthValidator.java b/code/src/main/java/org/nocturne/validation/BytesLengthValidator.java index 6b631d5..9c984c6 100644 --- a/code/src/main/java/org/nocturne/validation/BytesLengthValidator.java +++ b/code/src/main/java/org/nocturne/validation/BytesLengthValidator.java @@ -1,95 +1,95 @@ -package org.nocturne.validation; - -import org.apache.log4j.Logger; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * Validates that length of given value bytes is in specified range. - * - * @author Maxim Shipko (sladethe@gmail.com) - * Date: 17.04.15 - */ -public class BytesLengthValidator extends Validator { - private static final Logger logger = Logger.getLogger(BytesLengthValidator.class); - - private final int minimalLength; - private final int maximalLength; - private final Charset charset; - - /** - * @param minimalLength Minimal bytes length. - */ - public BytesLengthValidator(int minimalLength) { - this(minimalLength, Integer.MAX_VALUE, StandardCharsets.UTF_8); - } - - /** - * @param minimalLength Minimal bytes length. - * @param charset Charset to get bytes. - */ - public BytesLengthValidator(int minimalLength, Charset charset) { - this(minimalLength, Integer.MAX_VALUE, charset); - } - - /** - * @param minimalLength Minimal bytes length. - * @param maximalLength Maximal bytes length. - */ - public BytesLengthValidator(int minimalLength, int maximalLength) { - this(minimalLength, maximalLength, StandardCharsets.UTF_8); - } - - /** - * @param minimalLength Minimal bytes length. - * @param maximalLength Maximal bytes length. - * @param charset Charset to get bytes. - */ - public BytesLengthValidator(int minimalLength, int maximalLength, Charset charset) { - this.minimalLength = minimalLength; - this.maximalLength = maximalLength; - this.charset = charset; - } - - /** - * @param value Value to be analyzed. - * @throws ValidationException On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (value == null) { - if (minimalLength > 0) { - throw new ValidationException($( - "Field should contain at least {0,number,#} bytes", minimalLength - )); - } else { - logger.error("Value is `null` but minimalLength <= 0."); - throw new ValidationException($("Field should not be empty")); - } - } - - int length = value.getBytes(charset).length; - - if (length < minimalLength) { - throw new ValidationException($( - "Field should contain at least {0,number,#} bytes", minimalLength - )); - } - - if (length > maximalLength) { - throw new ValidationException($( - "Field should contain no more than {0,number,#} bytes", maximalLength - )); - } - } - - @Override - public String toString() { - return String.format( - "BytesLengthValidator {minimalLength=%d, maximalLength=%d}", minimalLength, maximalLength - ); - } -} +package org.nocturne.validation; + +import org.apache.log4j.Logger; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Validates that length of given value bytes is in specified range. + * + * @author Maxim Shipko (sladethe@gmail.com) + * Date: 17.04.15 + */ +public class BytesLengthValidator extends Validator { + private static final Logger logger = Logger.getLogger(BytesLengthValidator.class); + + private final int minimalLength; + private final int maximalLength; + private final Charset charset; + + /** + * @param minimalLength Minimal bytes length. + */ + public BytesLengthValidator(int minimalLength) { + this(minimalLength, Integer.MAX_VALUE, StandardCharsets.UTF_8); + } + + /** + * @param minimalLength Minimal bytes length. + * @param charset Charset to get bytes. + */ + public BytesLengthValidator(int minimalLength, Charset charset) { + this(minimalLength, Integer.MAX_VALUE, charset); + } + + /** + * @param minimalLength Minimal bytes length. + * @param maximalLength Maximal bytes length. + */ + public BytesLengthValidator(int minimalLength, int maximalLength) { + this(minimalLength, maximalLength, StandardCharsets.UTF_8); + } + + /** + * @param minimalLength Minimal bytes length. + * @param maximalLength Maximal bytes length. + * @param charset Charset to get bytes. + */ + public BytesLengthValidator(int minimalLength, int maximalLength, Charset charset) { + this.minimalLength = minimalLength; + this.maximalLength = maximalLength; + this.charset = charset; + } + + /** + * @param value Value to be analyzed. + * @throws ValidationException On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (value == null) { + if (minimalLength > 0) { + throw new ValidationException($( + "Field should contain at least {0,number,#} bytes", minimalLength + )); + } else { + logger.error("Value is `null` but minimalLength <= 0."); + throw new ValidationException($("Field should not be empty")); + } + } + + int length = value.getBytes(charset).length; + + if (length < minimalLength) { + throw new ValidationException($( + "Field should contain at least {0,number,#} bytes", minimalLength + )); + } + + if (length > maximalLength) { + throw new ValidationException($( + "Field should contain no more than {0,number,#} bytes", maximalLength + )); + } + } + + @Override + public String toString() { + return String.format( + "BytesLengthValidator {minimalLength=%d, maximalLength=%d}", minimalLength, maximalLength + ); + } +} diff --git a/code/src/main/java/org/nocturne/validation/EmailValidator.java b/code/src/main/java/org/nocturne/validation/EmailValidator.java index d001766..3f34f52 100644 --- a/code/src/main/java/org/nocturne/validation/EmailValidator.java +++ b/code/src/main/java/org/nocturne/validation/EmailValidator.java @@ -1,64 +1,64 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import org.nocturne.util.StringUtil; - -import java.util.regex.Pattern; - -/** - * Validates email. Doesn't use RFC. Use very loyal rules. - * - * @author Mike Mirzayanov - */ -public class EmailValidator extends Validator { - private static final Pattern EMAIL_PATTERN = Pattern.compile("[^@<>&\"\'/\\\\\\s]+@[^@<>&\"\'/\\\\\\s]+\\.[a-z]+"); - - /** - * Shortcut for error message. - */ - private final String message; - - /** - * Creates validator with default error message: Field should contain valid email. - */ - public EmailValidator() { - this("Field should contain valid email"); - } - - /** - * Creates validator with specific error message. - * Actually caption shortcut should be passed. - * - * @param message Error message caption shortcut. - */ - public EmailValidator(String message) { - this.message = message; - } - - @Override - public void run(String value) throws ValidationException { - boolean invalid = false; - - if (StringUtil.isEmpty(value)) { - invalid = true; - } - - if (!invalid) { - for (int i = 0; i < value.length(); i++) { - if (value.charAt(i) <= ' ') { - invalid = true; - } - } - } - - if (!invalid && !EMAIL_PATTERN.matcher(value).matches()) { - invalid = true; - } - - if (invalid) { - throw new ValidationException($(message)); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import org.nocturne.util.StringUtil; + +import java.util.regex.Pattern; + +/** + * Validates email. Doesn't use RFC. Use very loyal rules. + * + * @author Mike Mirzayanov + */ +public class EmailValidator extends Validator { + private static final Pattern EMAIL_PATTERN = Pattern.compile("[^@<>&\"\'/\\\\\\s]+@[^@<>&\"\'/\\\\\\s]+\\.[a-z]+"); + + /** + * Shortcut for error message. + */ + private final String message; + + /** + * Creates validator with default error message: Field should contain valid email. + */ + public EmailValidator() { + this("Field should contain valid email"); + } + + /** + * Creates validator with specific error message. + * Actually caption shortcut should be passed. + * + * @param message Error message caption shortcut. + */ + public EmailValidator(String message) { + this.message = message; + } + + @Override + public void run(String value) throws ValidationException { + boolean invalid = false; + + if (StringUtil.isEmpty(value)) { + invalid = true; + } + + if (!invalid) { + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) <= ' ') { + invalid = true; + } + } + } + + if (!invalid && !EMAIL_PATTERN.matcher(value).matches()) { + invalid = true; + } + + if (invalid) { + throw new ValidationException($(message)); + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/EnglishValidator.java b/code/src/main/java/org/nocturne/validation/EnglishValidator.java index 339dcb0..ed3a02d 100644 --- a/code/src/main/java/org/nocturne/validation/EnglishValidator.java +++ b/code/src/main/java/org/nocturne/validation/EnglishValidator.java @@ -1,43 +1,43 @@ -/* Copyright by Mike Mirzayanov. */ - -package org.nocturne.validation; - -/** - * Ensures that specified value is English. - * Checks that value doesn't contain characters with codes less than 9 and - * doesn't contain too many non-ascii characters. - * - * @author Mike Mirzayanov - */ -public class EnglishValidator extends Validator { - private String message; - - public EnglishValidator() { - } - - public EnglishValidator(String message) { - this.message = message; - } - - @Override - public void run(String value) throws ValidationException { - if (value != null) { - int specialCount = 0; - int nonAsciiCount = 0; - - for (char c : value.toCharArray()) { - if (c < 9) { - ++specialCount; - } - if (c > 127) { - ++nonAsciiCount; - } - } - - if (specialCount > 0 || nonAsciiCount > value.length() / 2) { - String msg = message != null ? message : $("Field should contain value in English"); - throw new ValidationException(msg); - } - } - } -} +/* Copyright by Mike Mirzayanov. */ + +package org.nocturne.validation; + +/** + * Ensures that specified value is English. + * Checks that value doesn't contain characters with codes less than 9 and + * doesn't contain too many non-ascii characters. + * + * @author Mike Mirzayanov + */ +public class EnglishValidator extends Validator { + private String message; + + public EnglishValidator() { + } + + public EnglishValidator(String message) { + this.message = message; + } + + @Override + public void run(String value) throws ValidationException { + if (value != null) { + int specialCount = 0; + int nonAsciiCount = 0; + + for (char c : value.toCharArray()) { + if (c < 9) { + ++specialCount; + } + if (c > 127) { + ++nonAsciiCount; + } + } + + if (specialCount > 0 || nonAsciiCount > value.length() / 2) { + String msg = message != null ? message : $("Field should contain value in English"); + throw new ValidationException(msg); + } + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/IntegerValidator.java b/code/src/main/java/org/nocturne/validation/IntegerValidator.java index cd28065..d6a24f2 100644 --- a/code/src/main/java/org/nocturne/validation/IntegerValidator.java +++ b/code/src/main/java/org/nocturne/validation/IntegerValidator.java @@ -1,72 +1,72 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import java.util.regex.Pattern; - -/** - * Validates that specified value is integer. Optionaly checks that value is in the range. - * - * @author Mike Mirzayanov - */ -public class IntegerValidator extends Validator { - private static final Pattern INTEGER_MATCH_PATTERN = Pattern.compile("[\\-]?[0-9]+"); - - /** - * Minimal integer value. - */ - private int minimalValue = Integer.MIN_VALUE; - - /** - * Maximal integer value. - */ - private int maximalValue = Integer.MAX_VALUE; - - /** - * Integer validator which doesn't check range. - */ - public IntegerValidator() { - } - - /** - * Checks that given value is in the range [minimalValue, maximalValue]. - * - * @param minimalValue min value. - * @param maximalValue max value. - */ - public IntegerValidator(int minimalValue, int maximalValue) { - this.minimalValue = minimalValue; - this.maximalValue = maximalValue; - } - - /** - * @param value Value to be analyzed. - * @throws ValidationException - * On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (value == null || !INTEGER_MATCH_PATTERN.matcher(value).matches()) { - throw new ValidationException($("Field should contain integer value")); - } - - int numeric; - - try { - numeric = Integer.parseInt(value); - } catch (Exception ignored) { - throw new ValidationException($("Field should contain integer value")); - } - - if (numeric < minimalValue) { - throw new ValidationException($("Field should be at least {0,number,#}", minimalValue)); - } - - if (numeric > maximalValue) { - throw new ValidationException($("Field should be no more than {0,number,#}", maximalValue)); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import java.util.regex.Pattern; + +/** + * Validates that specified value is integer. Optionaly checks that value is in the range. + * + * @author Mike Mirzayanov + */ +public class IntegerValidator extends Validator { + private static final Pattern INTEGER_MATCH_PATTERN = Pattern.compile("[\\-]?[0-9]+"); + + /** + * Minimal integer value. + */ + private int minimalValue = Integer.MIN_VALUE; + + /** + * Maximal integer value. + */ + private int maximalValue = Integer.MAX_VALUE; + + /** + * Integer validator which doesn't check range. + */ + public IntegerValidator() { + } + + /** + * Checks that given value is in the range [minimalValue, maximalValue]. + * + * @param minimalValue min value. + * @param maximalValue max value. + */ + public IntegerValidator(int minimalValue, int maximalValue) { + this.minimalValue = minimalValue; + this.maximalValue = maximalValue; + } + + /** + * @param value Value to be analyzed. + * @throws ValidationException + * On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (value == null || !INTEGER_MATCH_PATTERN.matcher(value).matches()) { + throw new ValidationException($("Field should contain integer value")); + } + + int numeric; + + try { + numeric = Integer.parseInt(value); + } catch (Exception ignored) { + throw new ValidationException($("Field should contain integer value")); + } + + if (numeric < minimalValue) { + throw new ValidationException($("Field should be at least {0,number,#}", minimalValue)); + } + + if (numeric > maximalValue) { + throw new ValidationException($("Field should be no more than {0,number,#}", maximalValue)); + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/LengthValidator.java b/code/src/main/java/org/nocturne/validation/LengthValidator.java index fceb513..82c85a4 100644 --- a/code/src/main/java/org/nocturne/validation/LengthValidator.java +++ b/code/src/main/java/org/nocturne/validation/LengthValidator.java @@ -1,83 +1,83 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import org.apache.log4j.Logger; - -/** - * Validates that given value length is in specified range. - * - * @author Mike Mirzayanov - */ -public class LengthValidator extends Validator { - private static final Logger logger = Logger.getLogger(LengthValidator.class); - - /** - * Minimal length. - */ - private int minimalLength = Integer.MIN_VALUE; - - /** - * Maximal length. - */ - private int maximalLength = Integer.MAX_VALUE; - - /** - * @param minimalLength Minimal length. - */ - public LengthValidator(int minimalLength) { - this.minimalLength = minimalLength; - } - - /** - * @param minimalLength Minimal length. - * @param maximalLength Maximal length. - */ - public LengthValidator(int minimalLength, int maximalLength) { - this.minimalLength = minimalLength; - this.maximalLength = maximalLength; - } - - /** - * @param value Value to be analyzed. - * @throws ValidationException - * On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (minimalLength >= 1 && value == null) { - throw new ValidationException( - $("Field should contain at least {0,number,#} characters", minimalLength) - ); - } - - if (minimalLength <= 0 && value == null) { - logger.error("Value is `null` but minimalLength <= 0."); - throw new ValidationException($("Field should not be empty")); - } - - int length = value.length(); - - if (length < minimalLength) { - throw new ValidationException( - $("Field should contain at least {0,number,#} characters", minimalLength) - ); - } - - if (length > maximalLength) { - throw new ValidationException( - $("Field should contain no more than {0,number,#} characters", maximalLength) - ); - } - } - - @Override - public String toString() { - return String.format( - "LengthValidator {minimalLength=%d, maximalLength=%d}", minimalLength, maximalLength - ); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import org.apache.log4j.Logger; + +/** + * Validates that given value length is in specified range. + * + * @author Mike Mirzayanov + */ +public class LengthValidator extends Validator { + private static final Logger logger = Logger.getLogger(LengthValidator.class); + + /** + * Minimal length. + */ + private int minimalLength = Integer.MIN_VALUE; + + /** + * Maximal length. + */ + private int maximalLength = Integer.MAX_VALUE; + + /** + * @param minimalLength Minimal length. + */ + public LengthValidator(int minimalLength) { + this.minimalLength = minimalLength; + } + + /** + * @param minimalLength Minimal length. + * @param maximalLength Maximal length. + */ + public LengthValidator(int minimalLength, int maximalLength) { + this.minimalLength = minimalLength; + this.maximalLength = maximalLength; + } + + /** + * @param value Value to be analyzed. + * @throws ValidationException + * On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (minimalLength >= 1 && value == null) { + throw new ValidationException( + $("Field should contain at least {0,number,#} characters", minimalLength) + ); + } + + if (minimalLength <= 0 && value == null) { + logger.error("Value is `null` but minimalLength <= 0."); + throw new ValidationException($("Field should not be empty")); + } + + int length = value.length(); + + if (length < minimalLength) { + throw new ValidationException( + $("Field should contain at least {0,number,#} characters", minimalLength) + ); + } + + if (length > maximalLength) { + throw new ValidationException( + $("Field should contain no more than {0,number,#} characters", maximalLength) + ); + } + } + + @Override + public String toString() { + return String.format( + "LengthValidator {minimalLength=%d, maximalLength=%d}", minimalLength, maximalLength + ); + } +} diff --git a/code/src/main/java/org/nocturne/validation/LongValidator.java b/code/src/main/java/org/nocturne/validation/LongValidator.java index ff201a5..0b20f91 100644 --- a/code/src/main/java/org/nocturne/validation/LongValidator.java +++ b/code/src/main/java/org/nocturne/validation/LongValidator.java @@ -1,72 +1,72 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import java.util.regex.Pattern; - -/** - * Validates that specified value is long. Optionaly checks that value is in the range. - * - * @author Maxim Shipko (sladethe@gmail.com) - * Date: 07.11.11 - */ -public class LongValidator extends Validator { - private static final Pattern LONG_MATCH_PATTERN = Pattern.compile("[\\-]?[0-9]+"); - - /** - * Minimal long value. - */ - private long minimalValue = Long.MIN_VALUE; - - /** - * Maximal long value. - */ - private long maximalValue = Long.MAX_VALUE; - - /** - * Long validator which doesn't check range. - */ - public LongValidator() { - } - - /** - * Checks that given value is in the range [minimalValue, maximalValue]. - * - * @param minimalValue min value. - * @param maximalValue max value. - */ - public LongValidator(long minimalValue, long maximalValue) { - this.minimalValue = minimalValue; - this.maximalValue = maximalValue; - } - - /** - * @param value Value to be analyzed. - * @throws ValidationException On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (value == null || !LONG_MATCH_PATTERN.matcher(value).matches()) { - throw new ValidationException($("Field should contain long integer value")); - } - - long numeric; - - try { - numeric = Long.parseLong(value); - } catch (Exception ignored) { - throw new ValidationException($("Field should contain long integer value")); - } - - if (numeric < minimalValue) { - throw new ValidationException($("Field should be at least {0,number,#}", minimalValue)); - } - - if (numeric > maximalValue) { - throw new ValidationException($("Field should be no more than {0,number,#}", maximalValue)); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import java.util.regex.Pattern; + +/** + * Validates that specified value is long. Optionaly checks that value is in the range. + * + * @author Maxim Shipko (sladethe@gmail.com) + * Date: 07.11.11 + */ +public class LongValidator extends Validator { + private static final Pattern LONG_MATCH_PATTERN = Pattern.compile("[\\-]?[0-9]+"); + + /** + * Minimal long value. + */ + private long minimalValue = Long.MIN_VALUE; + + /** + * Maximal long value. + */ + private long maximalValue = Long.MAX_VALUE; + + /** + * Long validator which doesn't check range. + */ + public LongValidator() { + } + + /** + * Checks that given value is in the range [minimalValue, maximalValue]. + * + * @param minimalValue min value. + * @param maximalValue max value. + */ + public LongValidator(long minimalValue, long maximalValue) { + this.minimalValue = minimalValue; + this.maximalValue = maximalValue; + } + + /** + * @param value Value to be analyzed. + * @throws ValidationException On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (value == null || !LONG_MATCH_PATTERN.matcher(value).matches()) { + throw new ValidationException($("Field should contain long integer value")); + } + + long numeric; + + try { + numeric = Long.parseLong(value); + } catch (Exception ignored) { + throw new ValidationException($("Field should contain long integer value")); + } + + if (numeric < minimalValue) { + throw new ValidationException($("Field should be at least {0,number,#}", minimalValue)); + } + + if (numeric > maximalValue) { + throw new ValidationException($("Field should be no more than {0,number,#}", maximalValue)); + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/NoUppercaseValidator.java b/code/src/main/java/org/nocturne/validation/NoUppercaseValidator.java index 719fd30..e7b03c3 100644 --- a/code/src/main/java/org/nocturne/validation/NoUppercaseValidator.java +++ b/code/src/main/java/org/nocturne/validation/NoUppercaseValidator.java @@ -1,17 +1,17 @@ -package org.nocturne.validation; - -/** - * Validates that parameter doesn't contain uppercase letters. - * - * @author Mike Mirzayanov - */ -public class NoUppercaseValidator extends Validator { - @Override - public void run(String value) throws ValidationException { - for (int i = 0; i < value.length(); ++i) { - if (Character.isUpperCase(value.charAt(i))) { - throw new ValidationException($("Field can't contain uppercase letters")); - } - } - } -} +package org.nocturne.validation; + +/** + * Validates that parameter doesn't contain uppercase letters. + * + * @author Mike Mirzayanov + */ +public class NoUppercaseValidator extends Validator { + @Override + public void run(String value) throws ValidationException { + for (int i = 0; i < value.length(); ++i) { + if (Character.isUpperCase(value.charAt(i))) { + throw new ValidationException($("Field can't contain uppercase letters")); + } + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/OptionValidator.java b/code/src/main/java/org/nocturne/validation/OptionValidator.java index 965b5ef..aa969ba 100644 --- a/code/src/main/java/org/nocturne/validation/OptionValidator.java +++ b/code/src/main/java/org/nocturne/validation/OptionValidator.java @@ -1,58 +1,58 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import java.util.Arrays; - -/** - * Validates that given value equals to one object from the given list. - * Uses object.toString().equals(value) to check equality. - * - * @author Mike Mirzayanov - */ -public class OptionValidator extends Validator { - /** - * Possible options. - */ - private final Object[] options; - - /** - * Constructs validator with given list of possible options. - * - * @param options List of options. - */ - public OptionValidator(Object... options) { - this.options = options; - } - - /** - * @param value Value to be analyzed. - * @throws ValidationException On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (value == null) { - for (Object option : options) { - if (option == null) { - return; - } - } - } else { - for (Object option : options) { - if (option != null && option.toString().equals(value)) { - return; - } - } - } - - throw new ValidationException($("Field contains unexpected value")); - } - - @Override - public String toString() { - return String.format("OptionValidator {options=%s}", Arrays.toString(options)); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import java.util.Arrays; + +/** + * Validates that given value equals to one object from the given list. + * Uses object.toString().equals(value) to check equality. + * + * @author Mike Mirzayanov + */ +public class OptionValidator extends Validator { + /** + * Possible options. + */ + private final Object[] options; + + /** + * Constructs validator with given list of possible options. + * + * @param options List of options. + */ + public OptionValidator(Object... options) { + this.options = options; + } + + /** + * @param value Value to be analyzed. + * @throws ValidationException On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (value == null) { + for (Object option : options) { + if (option == null) { + return; + } + } + } else { + for (Object option : options) { + if (option != null && option.toString().equals(value)) { + return; + } + } + } + + throw new ValidationException($("Field contains unexpected value")); + } + + @Override + public String toString() { + return String.format("OptionValidator {options=%s}", Arrays.toString(options)); + } +} diff --git a/code/src/main/java/org/nocturne/validation/PatternValidator.java b/code/src/main/java/org/nocturne/validation/PatternValidator.java index bfaa3ab..142857f 100644 --- a/code/src/main/java/org/nocturne/validation/PatternValidator.java +++ b/code/src/main/java/org/nocturne/validation/PatternValidator.java @@ -1,44 +1,44 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import java.util.regex.Pattern; - -/** - * Ensures that the given value is matched by specified regex pattern. - * - * @author Mike Mirzayanov - */ -public class PatternValidator extends Validator { - /** - * Regex pattern. - */ - private final Pattern pattern; - - /** - * Shortcut for error message. - */ - private final String message; - - /** - * @param pattern Validated strings will be checked to match the given pattern. - * @param message Error message caption shortcut. - */ - public PatternValidator(String pattern, String message) { - this.pattern = Pattern.compile(pattern); - this.message = message; - } - - @Override - public void run(String value) throws ValidationException { - if (!pattern.matcher(value).matches()) { - throw new ValidationException($(message)); - } - } - - @Override - public String toString() { - return String.format("PatternValidator {pattern='%s'}", pattern); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import java.util.regex.Pattern; + +/** + * Ensures that the given value is matched by specified regex pattern. + * + * @author Mike Mirzayanov + */ +public class PatternValidator extends Validator { + /** + * Regex pattern. + */ + private final Pattern pattern; + + /** + * Shortcut for error message. + */ + private final String message; + + /** + * @param pattern Validated strings will be checked to match the given pattern. + * @param message Error message caption shortcut. + */ + public PatternValidator(String pattern, String message) { + this.pattern = Pattern.compile(pattern); + this.message = message; + } + + @Override + public void run(String value) throws ValidationException { + if (!pattern.matcher(value).matches()) { + throw new ValidationException($(message)); + } + } + + @Override + public String toString() { + return String.format("PatternValidator {pattern='%s'}", pattern); + } +} diff --git a/code/src/main/java/org/nocturne/validation/RequiredValidator.java b/code/src/main/java/org/nocturne/validation/RequiredValidator.java index a12316f..a7c80f1 100644 --- a/code/src/main/java/org/nocturne/validation/RequiredValidator.java +++ b/code/src/main/java/org/nocturne/validation/RequiredValidator.java @@ -1,40 +1,40 @@ -/* Copyright by Mike Mirzayanov. */ -package org.nocturne.validation; - -import org.nocturne.util.StringUtil; - -/** - * Ensures that value is not null and not empty. - * - * @author Mike Mirzayanov - */ -public class RequiredValidator extends Validator { - /** - * Shortcut for error message. - */ - private final String message; - - /** - * Creates validator with default error message: Field should not be empty. - */ - public RequiredValidator() { - this("Field should not be empty"); - } - - /** - * Creates validator with specific error message. - * Actually caption shortcut should be passed. - * - * @param message Error message caption shortcut. - */ - public RequiredValidator(String message) { - this.message = message; - } - - @Override - public void run(String value) throws ValidationException { - if (StringUtil.isEmpty(value)) { - throw new ValidationException($(message)); - } - } -} +/* Copyright by Mike Mirzayanov. */ +package org.nocturne.validation; + +import org.nocturne.util.StringUtil; + +/** + * Ensures that value is not null and not empty. + * + * @author Mike Mirzayanov + */ +public class RequiredValidator extends Validator { + /** + * Shortcut for error message. + */ + private final String message; + + /** + * Creates validator with default error message: Field should not be empty. + */ + public RequiredValidator() { + this("Field should not be empty"); + } + + /** + * Creates validator with specific error message. + * Actually caption shortcut should be passed. + * + * @param message Error message caption shortcut. + */ + public RequiredValidator(String message) { + this.message = message; + } + + @Override + public void run(String value) throws ValidationException { + if (StringUtil.isEmpty(value)) { + throw new ValidationException($(message)); + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/StringCharactersValidator.java b/code/src/main/java/org/nocturne/validation/StringCharactersValidator.java index 562b0c7..c635987 100644 --- a/code/src/main/java/org/nocturne/validation/StringCharactersValidator.java +++ b/code/src/main/java/org/nocturne/validation/StringCharactersValidator.java @@ -1,45 +1,45 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -/** - * Ensures that all characters in string are from the given string (alphabet). - * - * @author Mike Mirzayanov - * @author Andrew Lazarev - */ -public class StringCharactersValidator extends Validator { - /** - * Alphabet. - */ - private final String alphabet; - - /** - * Shortcut for error message. - */ - private final String message; - - /** - * @param alphabet All characters from validated value will be checked to be in alphabet. - * @param message Error message caption shortcut. - */ - public StringCharactersValidator(String alphabet, String message) { - this.alphabet = alphabet; - this.message = message; - } - - @Override - public void run(String value) throws ValidationException { - for (int i = 0; i < value.length(); ++i) { - if (alphabet.indexOf(value.charAt(i)) == -1) { - throw new ValidationException($(message)); - } - } - } - - @Override - public String toString() { - return String.format("StringCharactersValidator {alphabet='%s'}", alphabet); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +/** + * Ensures that all characters in string are from the given string (alphabet). + * + * @author Mike Mirzayanov + * @author Andrew Lazarev + */ +public class StringCharactersValidator extends Validator { + /** + * Alphabet. + */ + private final String alphabet; + + /** + * Shortcut for error message. + */ + private final String message; + + /** + * @param alphabet All characters from validated value will be checked to be in alphabet. + * @param message Error message caption shortcut. + */ + public StringCharactersValidator(String alphabet, String message) { + this.alphabet = alphabet; + this.message = message; + } + + @Override + public void run(String value) throws ValidationException { + for (int i = 0; i < value.length(); ++i) { + if (alphabet.indexOf(value.charAt(i)) == -1) { + throw new ValidationException($(message)); + } + } + } + + @Override + public String toString() { + return String.format("StringCharactersValidator {alphabet='%s'}", alphabet); + } +} diff --git a/code/src/main/java/org/nocturne/validation/TextValidator.java b/code/src/main/java/org/nocturne/validation/TextValidator.java index f362b01..6ac6e46 100644 --- a/code/src/main/java/org/nocturne/validation/TextValidator.java +++ b/code/src/main/java/org/nocturne/validation/TextValidator.java @@ -1,80 +1,80 @@ -package org.nocturne.validation; - -import org.apache.commons.lang3.ArrayUtils; - -/** - * Checks that parameter doesn't contain binary characters (with codes less than 9). - * - * @author Mike Mirzayanov - */ -public class TextValidator extends Validator { - public static final double NON_STRICT_BINARY_DATA_MAX_RATIO = 0.1; - /** - * Shortcut for error message. - */ - private final String message; - - /** - * Parameters for message caption. - */ - private final Object[] messageParams; - - /** - * Strict validation disallows any binary data, non-strict allows some. - */ - private final boolean strict; - - /** - * Creates validator with default error message: Field can't contain binary data. - */ - public TextValidator() { - this(true); - } - - /** - * Creates validator with default error message: Field can't contain binary data. - * - * @param strict Strict model enable flag. - */ - public TextValidator(boolean strict) { - this(strict, "Field can't contain binary data", ArrayUtils.EMPTY_OBJECT_ARRAY); - } - - /** - * Creates validator with specific error message. - * Actually caption shortcut should be passed. - * - * @param message Error message caption shortcut. - * @param messageParams Message shortcut parameters. - */ - public TextValidator(String message, Object... messageParams) { - this(true, message, messageParams); - } - - /** - * Creates validator with specific error message. - * Actually caption shortcut should be passed. - * - * @param strict Strict model enable flag. - * @param message Error message caption shortcut. - * @param messageParams Message shortcut parameters. - */ - public TextValidator(boolean strict, String message, Object... messageParams) { - this.strict = strict; - this.message = message; - this.messageParams = messageParams; - } - - @Override - public void run(String value) throws ValidationException { - int binaryCount = 0; - for (int i = 0; i < value.length(); ++i) { - if (value.charAt(i) < 9) { - binaryCount++; - } - } - if ((strict && binaryCount != 0) || (!strict && binaryCount > value.length() * NON_STRICT_BINARY_DATA_MAX_RATIO)) { - throw new ValidationException($(message, messageParams)); - } - } -} +package org.nocturne.validation; + +import org.apache.commons.lang3.ArrayUtils; + +/** + * Checks that parameter doesn't contain binary characters (with codes less than 9). + * + * @author Mike Mirzayanov + */ +public class TextValidator extends Validator { + public static final double NON_STRICT_BINARY_DATA_MAX_RATIO = 0.1; + /** + * Shortcut for error message. + */ + private final String message; + + /** + * Parameters for message caption. + */ + private final Object[] messageParams; + + /** + * Strict validation disallows any binary data, non-strict allows some. + */ + private final boolean strict; + + /** + * Creates validator with default error message: Field can't contain binary data. + */ + public TextValidator() { + this(true); + } + + /** + * Creates validator with default error message: Field can't contain binary data. + * + * @param strict Strict model enable flag. + */ + public TextValidator(boolean strict) { + this(strict, "Field can't contain binary data", ArrayUtils.EMPTY_OBJECT_ARRAY); + } + + /** + * Creates validator with specific error message. + * Actually caption shortcut should be passed. + * + * @param message Error message caption shortcut. + * @param messageParams Message shortcut parameters. + */ + public TextValidator(String message, Object... messageParams) { + this(true, message, messageParams); + } + + /** + * Creates validator with specific error message. + * Actually caption shortcut should be passed. + * + * @param strict Strict model enable flag. + * @param message Error message caption shortcut. + * @param messageParams Message shortcut parameters. + */ + public TextValidator(boolean strict, String message, Object... messageParams) { + this.strict = strict; + this.message = message; + this.messageParams = messageParams; + } + + @Override + public void run(String value) throws ValidationException { + int binaryCount = 0; + for (int i = 0; i < value.length(); ++i) { + if (value.charAt(i) < 9) { + binaryCount++; + } + } + if ((strict && binaryCount != 0) || (!strict && binaryCount > value.length() * NON_STRICT_BINARY_DATA_MAX_RATIO)) { + throw new ValidationException($(message, messageParams)); + } + } +} diff --git a/code/src/main/java/org/nocturne/validation/ValidationException.java b/code/src/main/java/org/nocturne/validation/ValidationException.java index fcfa3d1..c84cd24 100644 --- a/code/src/main/java/org/nocturne/validation/ValidationException.java +++ b/code/src/main/java/org/nocturne/validation/ValidationException.java @@ -1,19 +1,19 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -/** - * Validators should throw this type of exceptions on validation error. - * - * @author Mike Mirzayanov - */ -@SuppressWarnings("DeserializableClassInSecureContext") -public class ValidationException extends Exception { - /** - * @param message Validation error message. Will be displayed for users. - */ - public ValidationException(String message) { - super(message); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +/** + * Validators should throw this type of exceptions on validation error. + * + * @author Mike Mirzayanov + */ +@SuppressWarnings("DeserializableClassInSecureContext") +public class ValidationException extends Exception { + /** + * @param message Validation error message. Will be displayed for users. + */ + public ValidationException(String message) { + super(message); + } +} diff --git a/code/src/main/java/org/nocturne/validation/Validator.java b/code/src/main/java/org/nocturne/validation/Validator.java index 65a6529..4695be2 100644 --- a/code/src/main/java/org/nocturne/validation/Validator.java +++ b/code/src/main/java/org/nocturne/validation/Validator.java @@ -1,45 +1,45 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import org.nocturne.main.ApplicationContext; - -/** - * Each validator should implement the interface. - * - * @author Mike Mirzayanov - */ -@SuppressWarnings("DollarSignInName") -public abstract class Validator { - /** - * @param value Value to be analyzed. - * @throws ValidationException On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - public abstract void run(String value) throws ValidationException; - - /** - * @param shortcut Shortcut value. - * @return The same as {@code ApplicationContext.getInstance().$()}. - */ - public String $(String shortcut) { - return ApplicationContext.getInstance().$(shortcut); - } - - /** - * @param shortcut Shortcut value. - * @param args Shortcut arguments. - * @return The same as {@code ApplicationContext.getInstance().$()}. - */ - @SuppressWarnings("OverloadedVarargsMethod") - public String $(String shortcut, Object... args) { - return ApplicationContext.getInstance().$(shortcut, args); - } - - @Override - public String toString() { - return getClass().getSimpleName() + " {}"; - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import org.nocturne.main.ApplicationContext; + +/** + * Each validator should implement the interface. + * + * @author Mike Mirzayanov + */ +@SuppressWarnings("DollarSignInName") +public abstract class Validator { + /** + * @param value Value to be analyzed. + * @throws ValidationException On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + public abstract void run(String value) throws ValidationException; + + /** + * @param shortcut Shortcut value. + * @return The same as {@code ApplicationContext.getInstance().$()}. + */ + public String $(String shortcut) { + return ApplicationContext.getInstance().$(shortcut); + } + + /** + * @param shortcut Shortcut value. + * @param args Shortcut arguments. + * @return The same as {@code ApplicationContext.getInstance().$()}. + */ + @SuppressWarnings("OverloadedVarargsMethod") + public String $(String shortcut, Object... args) { + return ApplicationContext.getInstance().$(shortcut, args); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " {}"; + } +} diff --git a/code/src/main/java/org/nocturne/validation/WordValidator.java b/code/src/main/java/org/nocturne/validation/WordValidator.java index 0797505..356816e 100644 --- a/code/src/main/java/org/nocturne/validation/WordValidator.java +++ b/code/src/main/java/org/nocturne/validation/WordValidator.java @@ -1,29 +1,29 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ -package org.nocturne.validation; - -import java.util.regex.Pattern; - -/** - * Checks that validated value is a single non-empty word (i.e. matches "\\w+"). - * - * @author Mike Mirzayanov - */ -public class WordValidator extends Validator { - private static final Pattern WORD_PATTERN = Pattern.compile("\\w+"); - - /** - * @param value Value to be analyzed. - * @throws ValidationException - * On validation error. It is good idea to pass - * localized via captions value inside ValidationException, - * like {@code return new ValidationException($("Field can't be empty"));}. - */ - @Override - public void run(String value) throws ValidationException { - if (!WORD_PATTERN.matcher(value).matches()) { - throw new ValidationException($("Field should contain letters, digits and underscore characters")); - } - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ +package org.nocturne.validation; + +import java.util.regex.Pattern; + +/** + * Checks that validated value is a single non-empty word (i.e. matches "\\w+"). + * + * @author Mike Mirzayanov + */ +public class WordValidator extends Validator { + private static final Pattern WORD_PATTERN = Pattern.compile("\\w+"); + + /** + * @param value Value to be analyzed. + * @throws ValidationException + * On validation error. It is good idea to pass + * localized via captions value inside ValidationException, + * like {@code return new ValidationException($("Field can't be empty"));}. + */ + @Override + public void run(String value) throws ValidationException { + if (!WORD_PATTERN.matcher(value).matches()) { + throw new ValidationException($("Field should contain letters, digits and underscore characters")); + } + } +} diff --git a/code/src/test/java/org/nocturne/GsonTest.java b/code/src/test/java/org/nocturne/GsonTest.java index ab79562..176a3d8 100644 --- a/code/src/test/java/org/nocturne/GsonTest.java +++ b/code/src/test/java/org/nocturne/GsonTest.java @@ -1,49 +1,49 @@ -package org.nocturne; - -import com.google.gson.Gson; -import junit.framework.TestCase; - -/** - * @author Maxim Shipko (sladethe@gmail.com) - * Date: 27.01.12 - */ -public class GsonTest extends TestCase { - public void testGson() throws Exception { - Gson gson = new Gson(); - - String valueA = "Can't release contest, because there are validation errors:
" - + "On testset 'tests' SAS detected 'stdin' as problem 'A' input file name, but specified 'asteroids.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'A' output file name, but specified 'asteroids.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'A': {{asteroids_rs.java: Can't compile solution file}, {asteroids_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {asteroids_as.java: Can't compile solution file}, {asteroids_mb.java: Can't compile solution file}, {asteroids_re.java: Can't compile solution file}, {asteroids_gk.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'B' input file name, but specified 'business.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'B' output file name, but specified 'business.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'B': {{business_rs_wa.dpr: Can't compile solution file}, {business_re.java: Can't compile solution file}, {business_gk_tl.java: Can't compile solution file}, {business_gk.java: Can't compile solution file}, {business_gk_wa.java: Can't compile solution file}, {business_ia_vd.java: Can't compile solution file}, {business_mb.java: Can't compile solution file}, {business_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {business_rs.dpr: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected some errors in problem 'C': {{check.dpr: Can't compile checker file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'D' input file name, but specified 'database.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'D' output file name, but specified 'database.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'D': {{database_ia.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {database_md.java: Can't compile solution file}, {database_re.java: Can't compile solution file}, {database_gk_ok_cpp_full_compact.cpp: Can't compile solution file}, {database_petr.java: Can't compile solution file}, {database_mb.java: Can't compile solution file}, {database_gk_ok_cpp_map_pair.cpp: Can't compile solution file}, {database_gk_ok_cpp_map_nested.cpp: Can't compile solution file}, {database_gk_ok_cpp_map_concat.cpp: Can't compile solution file}, {database_rs.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'E' input file name, but specified 'exclusive.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'E' output file name, but specified 'exclusive.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'E': {{exclusive_petr.java: Can't compile solution file}, {exclusive_rs.java: Can't compile solution file}, {exclusive_ft.java: Can't compile solution file}, {exclusive_mb.java: Can't compile solution file}, {exclusive_gk.java: Can't compile solution file}, {check.dpr: Can't compile checker file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'F' input file name, but specified 'funny.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'F' output file name, but specified 'funny.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'F': {{funny_gk_wa_skip_words.java: Can't compile solution file}, {funny_ia_wrong.java: Can't compile solution file}, {funny_rs_tl.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {funny_mb.java: Can't compile solution file}, {funny_ia.java: Can't compile solution file}, {funny_gk.java: Can't compile solution file}, {funny_gk_wa_short_eager.java: Can't compile solution file}, {funny_petr.java: Can't compile solution file}, {funny_gk_wa_no_generated.java: Can't compile solution file}, {funny_gk_wa_short.java: Can't compile solution file}, {funny_rs.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'G' input file name, but specified 'garbling.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'G' output file name, but specified 'garbling.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'G': {{check.dpr: Can't compile checker file}, {garbling_re.java: Can't compile solution file}, {garbling_rs.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'H' input file name, but specified 'headshot.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'H' output file name, but specified 'headshot.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'H': {{headshot_gk.java: Can't compile solution file}, {headshot_rs.dpr: Can't compile solution file}, {headshot_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {headshot_mb.java: Can't compile solution file}, {headshot_ia.java: Can't compile solution file}, {headshot_re.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'I' input file name, but specified 'inspection.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'I' output file name, but specified 'inspection.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'I': {{inspection_mb.java: Can't compile solution file}, {inspection_rs_wa.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {inspection_rs.java: Can't compile solution file}, {inspection_petr.java: Can't compile solution file}, {inspection_pm.java: Can't compile solution file}}.
" - + "On testset 'tests' SAS detected 'stdin' as problem 'J' input file name, but specified 'javacert.in'.
" - + "On testset 'tests' SAS detected 'stdout' as problem 'J' output file name, but specified 'javacert.out'.
" - + "On testset 'tests' SAS detected some errors in problem 'J': {{check.dpr: Can't compile checker file}, {javacert_re.java: Can't compile solution file}, {javacert_pm_round.java: Can't compile solution file}, {javacert_petr.java: Can't compile solution file}, {javacert_pm.java: Can't compile solution file}, {javacert_mb.java: Can't compile solution file}, {javacert_re_backtrack.java: Can't compile solution file}, {javacert_rs.java: Can't compile solution file}, {javacert_vd.java: Can't compile solution file}}.
"; - - assertEquals( - "Restored from JSON \'valueA\' does not equal to original.", - valueA, gson.fromJson(gson.toJson(valueA), String.class) - ); - } -} +package org.nocturne; + +import com.google.gson.Gson; +import junit.framework.TestCase; + +/** + * @author Maxim Shipko (sladethe@gmail.com) + * Date: 27.01.12 + */ +public class GsonTest extends TestCase { + public void testGson() throws Exception { + Gson gson = new Gson(); + + String valueA = "Can't release contest, because there are validation errors:
" + + "On testset 'tests' SAS detected 'stdin' as problem 'A' input file name, but specified 'asteroids.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'A' output file name, but specified 'asteroids.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'A': {{asteroids_rs.java: Can't compile solution file}, {asteroids_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {asteroids_as.java: Can't compile solution file}, {asteroids_mb.java: Can't compile solution file}, {asteroids_re.java: Can't compile solution file}, {asteroids_gk.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'B' input file name, but specified 'business.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'B' output file name, but specified 'business.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'B': {{business_rs_wa.dpr: Can't compile solution file}, {business_re.java: Can't compile solution file}, {business_gk_tl.java: Can't compile solution file}, {business_gk.java: Can't compile solution file}, {business_gk_wa.java: Can't compile solution file}, {business_ia_vd.java: Can't compile solution file}, {business_mb.java: Can't compile solution file}, {business_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {business_rs.dpr: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected some errors in problem 'C': {{check.dpr: Can't compile checker file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'D' input file name, but specified 'database.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'D' output file name, but specified 'database.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'D': {{database_ia.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {database_md.java: Can't compile solution file}, {database_re.java: Can't compile solution file}, {database_gk_ok_cpp_full_compact.cpp: Can't compile solution file}, {database_petr.java: Can't compile solution file}, {database_mb.java: Can't compile solution file}, {database_gk_ok_cpp_map_pair.cpp: Can't compile solution file}, {database_gk_ok_cpp_map_nested.cpp: Can't compile solution file}, {database_gk_ok_cpp_map_concat.cpp: Can't compile solution file}, {database_rs.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'E' input file name, but specified 'exclusive.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'E' output file name, but specified 'exclusive.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'E': {{exclusive_petr.java: Can't compile solution file}, {exclusive_rs.java: Can't compile solution file}, {exclusive_ft.java: Can't compile solution file}, {exclusive_mb.java: Can't compile solution file}, {exclusive_gk.java: Can't compile solution file}, {check.dpr: Can't compile checker file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'F' input file name, but specified 'funny.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'F' output file name, but specified 'funny.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'F': {{funny_gk_wa_skip_words.java: Can't compile solution file}, {funny_ia_wrong.java: Can't compile solution file}, {funny_rs_tl.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {funny_mb.java: Can't compile solution file}, {funny_ia.java: Can't compile solution file}, {funny_gk.java: Can't compile solution file}, {funny_gk_wa_short_eager.java: Can't compile solution file}, {funny_petr.java: Can't compile solution file}, {funny_gk_wa_no_generated.java: Can't compile solution file}, {funny_gk_wa_short.java: Can't compile solution file}, {funny_rs.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'G' input file name, but specified 'garbling.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'G' output file name, but specified 'garbling.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'G': {{check.dpr: Can't compile checker file}, {garbling_re.java: Can't compile solution file}, {garbling_rs.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'H' input file name, but specified 'headshot.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'H' output file name, but specified 'headshot.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'H': {{headshot_gk.java: Can't compile solution file}, {headshot_rs.dpr: Can't compile solution file}, {headshot_petr.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {headshot_mb.java: Can't compile solution file}, {headshot_ia.java: Can't compile solution file}, {headshot_re.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'I' input file name, but specified 'inspection.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'I' output file name, but specified 'inspection.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'I': {{inspection_mb.java: Can't compile solution file}, {inspection_rs_wa.java: Can't compile solution file}, {check.dpr: Can't compile checker file}, {inspection_rs.java: Can't compile solution file}, {inspection_petr.java: Can't compile solution file}, {inspection_pm.java: Can't compile solution file}}.
" + + "On testset 'tests' SAS detected 'stdin' as problem 'J' input file name, but specified 'javacert.in'.
" + + "On testset 'tests' SAS detected 'stdout' as problem 'J' output file name, but specified 'javacert.out'.
" + + "On testset 'tests' SAS detected some errors in problem 'J': {{check.dpr: Can't compile checker file}, {javacert_re.java: Can't compile solution file}, {javacert_pm_round.java: Can't compile solution file}, {javacert_petr.java: Can't compile solution file}, {javacert_pm.java: Can't compile solution file}, {javacert_mb.java: Can't compile solution file}, {javacert_re_backtrack.java: Can't compile solution file}, {javacert_rs.java: Can't compile solution file}, {javacert_vd.java: Can't compile solution file}}.
"; + + assertEquals( + "Restored from JSON \'valueA\' does not equal to original.", + valueA, gson.fromJson(gson.toJson(valueA), String.class) + ); + } +} diff --git a/code/src/test/java/org/nocturne/link/LinksTest.java b/code/src/test/java/org/nocturne/link/LinksTest.java index e119c79..97143f8 100644 --- a/code/src/test/java/org/nocturne/link/LinksTest.java +++ b/code/src/test/java/org/nocturne/link/LinksTest.java @@ -1,212 +1,212 @@ -package org.nocturne.link; - -import junit.framework.TestCase; -import org.nocturne.exception.ConfigurationException; -import org.nocturne.link.pages.*; -import org.nocturne.main.ApplicationContextHelper; -import org.nocturne.main.Page; - -import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Mike Mirzayanov - */ -public class LinksTest extends TestCase { - private static final String CONTEXT_PATH = "http://code.google.com/nocturne"; - - static { - ApplicationContextHelper.setContextPath(CONTEXT_PATH); - - Links.add(IndexPage.class); - Links.add(LongLinkedPage.class); - Links.add(NewsPage.class); - Links.add(ProfilePage.class); - Links.add(SectionsPage.class); - } - - public void testIndexPage() { - internalTestIndexPageWithoutInterceptors(); - - Links.addInterceptor("testInterceptor", new Links.Interceptor() { - @Override - public String postprocess( - String link, Class clazz, @Nullable String linkName, Map params) { - return link.replace("vip", "test"); - } - }); - - assertEquals(CONTEXT_PATH + '/', Links.getLink("IndexPage")); - assertEquals(CONTEXT_PATH + "/page/17", Links.getLink("IndexPage", "pageIndex", 17)); - assertEquals(CONTEXT_PATH + "/page/17?i=3", Links.getLink(IndexPage.class, "pageIndex", 17, "i", 3)); - assertEquals(CONTEXT_PATH + '/', Links.getLinkByMap(IndexPage.class, null, Collections.emptyMap())); - assertEquals(CONTEXT_PATH + "/testIndex", Links.getLinkByMap(IndexPage.class, "vipLink", Collections.emptyMap())); - - Links.removeInterceptor("testInterceptor"); - - internalTestIndexPageWithoutInterceptors(); - } - - private static void internalTestIndexPageWithoutInterceptors() { - assertEquals(CONTEXT_PATH + '/', Links.getLink("IndexPage")); - assertEquals(CONTEXT_PATH + "/page/17", Links.getLink("IndexPage", "pageIndex", 17)); - assertEquals(CONTEXT_PATH + "/page/17?i=3", Links.getLink(IndexPage.class, "pageIndex", 17, "i", 3)); - assertEquals(CONTEXT_PATH + '/', Links.getLinkByMap(IndexPage.class, null, Collections.emptyMap())); - assertEquals(CONTEXT_PATH + "/vipIndex", Links.getLinkByMap(IndexPage.class, "vipLink", Collections.emptyMap())); - - assertEqualsLinkMatchResultWithName(Links.match("/index"), IndexPage.class, "index", null); - assertEqualsLinkMatchResultWithName(Links.match("/index?a=b"), IndexPage.class, "index", null); - assertEqualsLinkMatchResultWithName(Links.match("/index/a"), null, null, null); - assertEqualsLinkMatchResultWithName(Links.match("/"), IndexPage.class, "", null); - assertEqualsLinkMatchResultWithName(Links.match("/page/17"), IndexPage.class, "page/{pageIndex}", null, "pageIndex", "17"); - assertEqualsLinkMatchResultWithName(Links.match("/index/page/17"), IndexPage.class, "index/page/{pageIndex}", null, "pageIndex", "17"); - assertEqualsLinkMatchResultWithName(Links.match("/vipIndex"), IndexPage.class, "vipIndex", "vipLink"); - assertEqualsLinkMatchResultWithName(Links.match("/vipPage/1"), IndexPage.class, "vipPage/{pageIndex}", "vipLink", "pageIndex", "1"); - } - - public void testLongLinkedPage() { - assertThrows(new Invokable() { - @Override - public void invoke() { - Links.getLink("LongLinkedPage"); - } - }, Links.NoSuchLinkException.class); - - assertEquals(CONTEXT_PATH + "/LongLinkedPage/a/1/b/2/c/3", Links.getLink("LongLinkedPage", "a", 1, "b", 2, "c", 3L)); - assertEquals(CONTEXT_PATH + "/LongLinkedPage/a/1/b/2/c/3?cc=cc2", Links.getLink("LongLinkedPage", "b", 2, "a", 1, "cc", "cc2", "c", 3L)); - assertEquals( - CONTEXT_PATH + "/LongLinkedPage/a/aa/b/bb/c/cc?a=aaa&a=4a&c=ccc&c=4c&c=5c&d=-3&d=-2&d=-1", - Links.getLink( - "LongLinkedPage", - "a", new String[]{"aa", "aaa", "4a"}, - "b", "bb", - "c", Arrays.asList("cc", "ccc", "4c", "5c"), - "d", new byte[]{-3, -2, -1} - ) - ); - assertEqualsLinkMatchResult(Links.match("/LongLinkedPage/a/1/b/2/c/3"), LongLinkedPage.class, "LongLinkedPage/a/{a}/b/{b}/c/{c}", "a", "1", "b", "2", "c", "3"); - } - - public void testNewsPage() { - assertThrows(new Invokable() { - @Override - public void invoke() { - Links.getLink(" news"); - } - }, Links.NoSuchLinkException.class); - - assertEquals(CONTEXT_PATH + "/news", Links.getLink(NewsPage.class)); - assertEqualsLinkMatchResult(Links.match("/news#12"), NewsPage.class, "news"); - assertEqualsLinkMatchResult(Links.match("/news?a=1&v=2233"), NewsPage.class, "news"); - } - - public void testProfilePage() { - assertEquals(CONTEXT_PATH + "/profile/Mike", Links.getLink(ProfilePage.class, "userName", "Mike")); - assertEquals(CONTEXT_PATH + "/profiles/all", Links.getLink(ProfilePage.class, "userName", "")); - assertEquals(CONTEXT_PATH + "/profiles/all", Links.getLink(ProfilePage.class, "userName", null)); - assertEqualsLinkMatchResult(Links.match("/profile/Mike"), ProfilePage.class, "profile/{userName:Mike,Max}", "userName", "Mike"); - assertEqualsLinkMatchResult(Links.match("/profile/Max"), ProfilePage.class, "profile/{userName:Mike,Max}", "userName", "Max"); - assertEqualsLinkMatchResult(Links.match("/profile/MikeMax"), null, null); - assertEqualsLinkMatchResult(Links.match("/profile/%20Mike"), null, null); - assertEqualsLinkMatchResult(Links.match("/profile/ Mike "), null, null); - assertEqualsLinkMatchResult(Links.match("/profiles/all"), ProfilePage.class, "profiles/all"); - assertEqualsLinkMatchResult(Links.match("/profiles/all/Max"), null, null); - assertEqualsLinkMatchResult(Links.match("/profiles"), null, null); - assertEqualsLinkMatchResult(Links.match("/profile"), null, null); - assertEqualsLinkMatchResult(Links.match("/profile/Mike/Mirzayanov"), null, null); - } - - public void testSectionsPage() { - assertThrows(new Invokable() { - @Override - public void invoke() { - Links.getLink(SectionsPage.class, "sectionId", "11"); - } - }, Links.NoSuchLinkException.class); - - assertEquals(CONTEXT_PATH + "/sections/01", Links.getLink(SectionsPage.class, "sectionId", "01")); - assertEquals(CONTEXT_PATH + "/sections/11", Links.getLink(SectionsPage.class, "sectionName", "11")); - - assertEquals("SectionsPageBySectionId", Links.match("/sections/01").getLink().name()); - assertEquals("SectionsPageBySectionName", Links.match("/sections/11").getLink().name()); - } - - public void testOneMoreIndexPage() { - assertThrows(new Invokable() { - @Override - public void invoke() { - Links.add(OneMoreIndexPage.class); - } - }, ConfigurationException.class); - } - - private static void assertEqualsLinkMatchResult( - LinkMatchResult linkMatchResult, @Nullable Class clazz, @Nullable String pattern, - String... attributes) { - if (clazz == null) { - assertNull(linkMatchResult); - return; - } - - assertEquals(clazz, linkMatchResult.getPageClass()); - assertEquals(pattern, linkMatchResult.getPattern()); - assertEquals(convertArrayToMap(attributes), linkMatchResult.getAttributes()); - } - - private static void assertEqualsLinkMatchResultWithName( - LinkMatchResult linkMatchResult, @Nullable Class clazz, @Nullable String pattern, - @Nullable String linkName, String... attributes) { - if (clazz == null) { - assertNull(linkMatchResult); - return; - } - - assertEquals(clazz, linkMatchResult.getPageClass()); - assertEquals(pattern, linkMatchResult.getPattern()); - assertEquals(convertArrayToMap(attributes), linkMatchResult.getAttributes()); - assertEquals( - linkName == null ? "" : linkName, - linkMatchResult.getLink().name() == null ? "" : linkMatchResult.getLink().name() - ); - } - - private static Map convertArrayToMap(String... params) { - if (params.length % 2 != 0) { - throw new IllegalArgumentException("Params should contain even number of elements."); - } - - Map map = new HashMap<>(); - - boolean isKey = true; - String key = null; - for (String param : params) { - if (isKey) { - key = param; - } else { - map.put(key, param); - } - isKey ^= true; - } - return map; - } - - private static void assertThrows(Invokable invokable, Class throwableClass) { - Throwable throwable = null; - - try { - invokable.invoke(); - } catch (Throwable e) { - throwable = e; - } - - assertNotNull(throwable); - assertEquals(throwableClass, throwable.getClass()); - } - - private interface Invokable { - void invoke() throws Throwable; - } -} +package org.nocturne.link; + +import junit.framework.TestCase; +import org.nocturne.exception.ConfigurationException; +import org.nocturne.link.pages.*; +import org.nocturne.main.ApplicationContextHelper; +import org.nocturne.main.Page; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mike Mirzayanov + */ +public class LinksTest extends TestCase { + private static final String CONTEXT_PATH = "http://code.google.com/nocturne"; + + static { + ApplicationContextHelper.setContextPath(CONTEXT_PATH); + + Links.add(IndexPage.class); + Links.add(LongLinkedPage.class); + Links.add(NewsPage.class); + Links.add(ProfilePage.class); + Links.add(SectionsPage.class); + } + + public void testIndexPage() { + internalTestIndexPageWithoutInterceptors(); + + Links.addInterceptor("testInterceptor", new Links.Interceptor() { + @Override + public String postprocess( + String link, Class clazz, @Nullable String linkName, Map params) { + return link.replace("vip", "test"); + } + }); + + assertEquals(CONTEXT_PATH + '/', Links.getLink("IndexPage")); + assertEquals(CONTEXT_PATH + "/page/17", Links.getLink("IndexPage", "pageIndex", 17)); + assertEquals(CONTEXT_PATH + "/page/17?i=3", Links.getLink(IndexPage.class, "pageIndex", 17, "i", 3)); + assertEquals(CONTEXT_PATH + '/', Links.getLinkByMap(IndexPage.class, null, Collections.emptyMap())); + assertEquals(CONTEXT_PATH + "/testIndex", Links.getLinkByMap(IndexPage.class, "vipLink", Collections.emptyMap())); + + Links.removeInterceptor("testInterceptor"); + + internalTestIndexPageWithoutInterceptors(); + } + + private static void internalTestIndexPageWithoutInterceptors() { + assertEquals(CONTEXT_PATH + '/', Links.getLink("IndexPage")); + assertEquals(CONTEXT_PATH + "/page/17", Links.getLink("IndexPage", "pageIndex", 17)); + assertEquals(CONTEXT_PATH + "/page/17?i=3", Links.getLink(IndexPage.class, "pageIndex", 17, "i", 3)); + assertEquals(CONTEXT_PATH + '/', Links.getLinkByMap(IndexPage.class, null, Collections.emptyMap())); + assertEquals(CONTEXT_PATH + "/vipIndex", Links.getLinkByMap(IndexPage.class, "vipLink", Collections.emptyMap())); + + assertEqualsLinkMatchResultWithName(Links.match("/index"), IndexPage.class, "index", null); + assertEqualsLinkMatchResultWithName(Links.match("/index?a=b"), IndexPage.class, "index", null); + assertEqualsLinkMatchResultWithName(Links.match("/index/a"), null, null, null); + assertEqualsLinkMatchResultWithName(Links.match("/"), IndexPage.class, "", null); + assertEqualsLinkMatchResultWithName(Links.match("/page/17"), IndexPage.class, "page/{pageIndex}", null, "pageIndex", "17"); + assertEqualsLinkMatchResultWithName(Links.match("/index/page/17"), IndexPage.class, "index/page/{pageIndex}", null, "pageIndex", "17"); + assertEqualsLinkMatchResultWithName(Links.match("/vipIndex"), IndexPage.class, "vipIndex", "vipLink"); + assertEqualsLinkMatchResultWithName(Links.match("/vipPage/1"), IndexPage.class, "vipPage/{pageIndex}", "vipLink", "pageIndex", "1"); + } + + public void testLongLinkedPage() { + assertThrows(new Invokable() { + @Override + public void invoke() { + Links.getLink("LongLinkedPage"); + } + }, Links.NoSuchLinkException.class); + + assertEquals(CONTEXT_PATH + "/LongLinkedPage/a/1/b/2/c/3", Links.getLink("LongLinkedPage", "a", 1, "b", 2, "c", 3L)); + assertEquals(CONTEXT_PATH + "/LongLinkedPage/a/1/b/2/c/3?cc=cc2", Links.getLink("LongLinkedPage", "b", 2, "a", 1, "cc", "cc2", "c", 3L)); + assertEquals( + CONTEXT_PATH + "/LongLinkedPage/a/aa/b/bb/c/cc?a=aaa&a=4a&c=ccc&c=4c&c=5c&d=-3&d=-2&d=-1", + Links.getLink( + "LongLinkedPage", + "a", new String[]{"aa", "aaa", "4a"}, + "b", "bb", + "c", Arrays.asList("cc", "ccc", "4c", "5c"), + "d", new byte[]{-3, -2, -1} + ) + ); + assertEqualsLinkMatchResult(Links.match("/LongLinkedPage/a/1/b/2/c/3"), LongLinkedPage.class, "LongLinkedPage/a/{a}/b/{b}/c/{c}", "a", "1", "b", "2", "c", "3"); + } + + public void testNewsPage() { + assertThrows(new Invokable() { + @Override + public void invoke() { + Links.getLink(" news"); + } + }, Links.NoSuchLinkException.class); + + assertEquals(CONTEXT_PATH + "/news", Links.getLink(NewsPage.class)); + assertEqualsLinkMatchResult(Links.match("/news#12"), NewsPage.class, "news"); + assertEqualsLinkMatchResult(Links.match("/news?a=1&v=2233"), NewsPage.class, "news"); + } + + public void testProfilePage() { + assertEquals(CONTEXT_PATH + "/profile/Mike", Links.getLink(ProfilePage.class, "userName", "Mike")); + assertEquals(CONTEXT_PATH + "/profiles/all", Links.getLink(ProfilePage.class, "userName", "")); + assertEquals(CONTEXT_PATH + "/profiles/all", Links.getLink(ProfilePage.class, "userName", null)); + assertEqualsLinkMatchResult(Links.match("/profile/Mike"), ProfilePage.class, "profile/{userName:Mike,Max}", "userName", "Mike"); + assertEqualsLinkMatchResult(Links.match("/profile/Max"), ProfilePage.class, "profile/{userName:Mike,Max}", "userName", "Max"); + assertEqualsLinkMatchResult(Links.match("/profile/MikeMax"), null, null); + assertEqualsLinkMatchResult(Links.match("/profile/%20Mike"), null, null); + assertEqualsLinkMatchResult(Links.match("/profile/ Mike "), null, null); + assertEqualsLinkMatchResult(Links.match("/profiles/all"), ProfilePage.class, "profiles/all"); + assertEqualsLinkMatchResult(Links.match("/profiles/all/Max"), null, null); + assertEqualsLinkMatchResult(Links.match("/profiles"), null, null); + assertEqualsLinkMatchResult(Links.match("/profile"), null, null); + assertEqualsLinkMatchResult(Links.match("/profile/Mike/Mirzayanov"), null, null); + } + + public void testSectionsPage() { + assertThrows(new Invokable() { + @Override + public void invoke() { + Links.getLink(SectionsPage.class, "sectionId", "11"); + } + }, Links.NoSuchLinkException.class); + + assertEquals(CONTEXT_PATH + "/sections/01", Links.getLink(SectionsPage.class, "sectionId", "01")); + assertEquals(CONTEXT_PATH + "/sections/11", Links.getLink(SectionsPage.class, "sectionName", "11")); + + assertEquals("SectionsPageBySectionId", Links.match("/sections/01").getLink().name()); + assertEquals("SectionsPageBySectionName", Links.match("/sections/11").getLink().name()); + } + + public void testOneMoreIndexPage() { + assertThrows(new Invokable() { + @Override + public void invoke() { + Links.add(OneMoreIndexPage.class); + } + }, ConfigurationException.class); + } + + private static void assertEqualsLinkMatchResult( + LinkMatchResult linkMatchResult, @Nullable Class clazz, @Nullable String pattern, + String... attributes) { + if (clazz == null) { + assertNull(linkMatchResult); + return; + } + + assertEquals(clazz, linkMatchResult.getPageClass()); + assertEquals(pattern, linkMatchResult.getPattern()); + assertEquals(convertArrayToMap(attributes), linkMatchResult.getAttributes()); + } + + private static void assertEqualsLinkMatchResultWithName( + LinkMatchResult linkMatchResult, @Nullable Class clazz, @Nullable String pattern, + @Nullable String linkName, String... attributes) { + if (clazz == null) { + assertNull(linkMatchResult); + return; + } + + assertEquals(clazz, linkMatchResult.getPageClass()); + assertEquals(pattern, linkMatchResult.getPattern()); + assertEquals(convertArrayToMap(attributes), linkMatchResult.getAttributes()); + assertEquals( + linkName == null ? "" : linkName, + linkMatchResult.getLink().name() == null ? "" : linkMatchResult.getLink().name() + ); + } + + private static Map convertArrayToMap(String... params) { + if (params.length % 2 != 0) { + throw new IllegalArgumentException("Params should contain even number of elements."); + } + + Map map = new HashMap<>(); + + boolean isKey = true; + String key = null; + for (String param : params) { + if (isKey) { + key = param; + } else { + map.put(key, param); + } + isKey ^= true; + } + return map; + } + + private static void assertThrows(Invokable invokable, Class throwableClass) { + Throwable throwable = null; + + try { + invokable.invoke(); + } catch (Throwable e) { + throwable = e; + } + + assertNotNull(throwable); + assertEquals(throwableClass, throwable.getClass()); + } + + private interface Invokable { + void invoke() throws Throwable; + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/IndexPage.java b/code/src/test/java/org/nocturne/link/pages/IndexPage.java index 77cd283..14a2622 100644 --- a/code/src/test/java/org/nocturne/link/pages/IndexPage.java +++ b/code/src/test/java/org/nocturne/link/pages/IndexPage.java @@ -1,18 +1,18 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.link.LinkSet; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@LinkSet({ - @Link(value = ";index;page/{pageIndex};index/page/{pageIndex}"), - @Link(value = "vipIndex;vipPage/{pageIndex}", name = "vipLink") -}) -public class IndexPage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.link.LinkSet; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@LinkSet({ + @Link(value = ";index;page/{pageIndex};index/page/{pageIndex}"), + @Link(value = "vipIndex;vipPage/{pageIndex}", name = "vipLink") +}) +public class IndexPage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/LongLinkedPage.java b/code/src/test/java/org/nocturne/link/pages/LongLinkedPage.java index dfce62d..6aa3d80 100644 --- a/code/src/test/java/org/nocturne/link/pages/LongLinkedPage.java +++ b/code/src/test/java/org/nocturne/link/pages/LongLinkedPage.java @@ -1,14 +1,14 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@Link("LongLinkedPage/a/{a}/b/{b}/c/{c}") -public class LongLinkedPage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@Link("LongLinkedPage/a/{a}/b/{b}/c/{c}") +public class LongLinkedPage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/NewsPage.java b/code/src/test/java/org/nocturne/link/pages/NewsPage.java index fb8d3ea..dcf7f65 100644 --- a/code/src/test/java/org/nocturne/link/pages/NewsPage.java +++ b/code/src/test/java/org/nocturne/link/pages/NewsPage.java @@ -1,14 +1,14 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@Link("news") -public class NewsPage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@Link("news") +public class NewsPage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/OneMoreIndexPage.java b/code/src/test/java/org/nocturne/link/pages/OneMoreIndexPage.java index 4804823..79de1ec 100644 --- a/code/src/test/java/org/nocturne/link/pages/OneMoreIndexPage.java +++ b/code/src/test/java/org/nocturne/link/pages/OneMoreIndexPage.java @@ -1,14 +1,14 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@Link("index") -public class OneMoreIndexPage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@Link("index") +public class OneMoreIndexPage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/ProfilePage.java b/code/src/test/java/org/nocturne/link/pages/ProfilePage.java index 413fbec..45e5848 100644 --- a/code/src/test/java/org/nocturne/link/pages/ProfilePage.java +++ b/code/src/test/java/org/nocturne/link/pages/ProfilePage.java @@ -1,14 +1,14 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@Link(value = "profile/{userName:Mike,Max};profiles/all", name = "ProfilePage") -public class ProfilePage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@Link(value = "profile/{userName:Mike,Max};profiles/all", name = "ProfilePage") +public class ProfilePage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/link/pages/SectionsPage.java b/code/src/test/java/org/nocturne/link/pages/SectionsPage.java index a2587e3..cf50a0a 100644 --- a/code/src/test/java/org/nocturne/link/pages/SectionsPage.java +++ b/code/src/test/java/org/nocturne/link/pages/SectionsPage.java @@ -1,18 +1,18 @@ -package org.nocturne.link.pages; - -import org.nocturne.link.Link; -import org.nocturne.link.LinkSet; -import org.nocturne.main.Page; - -/** - * @author Mike Mirzayanov - */ -@LinkSet({ - @Link(value = "sections/{sectionId:1,2,3,01,02,03}", name = "SectionsPageBySectionId"), - @Link(value = "sections/{sectionName}", name = "SectionsPageBySectionName") -}) -public class SectionsPage extends Page { - @Override - public void action() { - } -} +package org.nocturne.link.pages; + +import org.nocturne.link.Link; +import org.nocturne.link.LinkSet; +import org.nocturne.main.Page; + +/** + * @author Mike Mirzayanov + */ +@LinkSet({ + @Link(value = "sections/{sectionId:1,2,3,01,02,03}", name = "SectionsPageBySectionId"), + @Link(value = "sections/{sectionName}", name = "SectionsPageBySectionName") +}) +public class SectionsPage extends Page { + @Override + public void action() { + } +} diff --git a/code/src/test/java/org/nocturne/main/ApplicationContextHelper.java b/code/src/test/java/org/nocturne/main/ApplicationContextHelper.java index c582de3..138478a 100644 --- a/code/src/test/java/org/nocturne/main/ApplicationContextHelper.java +++ b/code/src/test/java/org/nocturne/main/ApplicationContextHelper.java @@ -1,10 +1,10 @@ -package org.nocturne.main; - -/** - * @author Mike Mirzayanov - */ -public class ApplicationContextHelper { - public static void setContextPath(String contextPath) { - ApplicationContext.getInstance().setContextPath(contextPath); - } -} +package org.nocturne.main; + +/** + * @author Mike Mirzayanov + */ +public class ApplicationContextHelper { + public static void setContextPath(String contextPath) { + ApplicationContext.getInstance().setContextPath(contextPath); + } +} diff --git a/dreamcatcher/READ.ME b/dreamcatcher/READ.ME index 931d9c5..d533cf3 100644 --- a/dreamcatcher/READ.ME +++ b/dreamcatcher/READ.ME @@ -1,18 +1,18 @@ -Dreamcatcher is optional tool for Nocturne to monitor class-files changes and hot-swap reloaded classes using JVM hot-swap feature. - -Usage - -Add to command line option like: - -javaagent:P:\nocturne\dreamcatcher\target\dreamcatcher-1.0-SNAPSHOT-jar-with-dependencies.jar - -It will split all the arguments by ";" to setup directories to listen, so you may use: - -javaagent:P:\nocturne\dreamcatcher\target\dreamcatcher-1.0-SNAPSHOT-jar-with-dependencies.jar=\ - P:\polygon\trunk\code\web\target\classes;P:\polygon\trunk\code\model\target\classes - -Another way to setup listen directories is to use system property "dreamcatcher.listen-directories" like: - if ("true".equals(System.getProperty("dreamcatcher.loaded"))) { - ((Set)System.getProperties().get("dreamcatcher.listen-directories")) - .add(classesDir); - } - -Nocturne (for multi-module applications) will add all the directories from module.xml:module->debug->directories->classes +Dreamcatcher is optional tool for Nocturne to monitor class-files changes and hot-swap reloaded classes using JVM hot-swap feature. + +Usage + +Add to command line option like: + -javaagent:P:\nocturne\dreamcatcher\target\dreamcatcher-1.0-SNAPSHOT-jar-with-dependencies.jar + +It will split all the arguments by ";" to setup directories to listen, so you may use: + -javaagent:P:\nocturne\dreamcatcher\target\dreamcatcher-1.0-SNAPSHOT-jar-with-dependencies.jar=\ + P:\polygon\trunk\code\web\target\classes;P:\polygon\trunk\code\model\target\classes + +Another way to setup listen directories is to use system property "dreamcatcher.listen-directories" like: + if ("true".equals(System.getProperty("dreamcatcher.loaded"))) { + ((Set)System.getProperties().get("dreamcatcher.listen-directories")) + .add(classesDir); + } + +Nocturne (for multi-module applications) will add all the directories from module.xml:module->debug->directories->classes diff --git a/dreamcatcher/bundle.bat b/dreamcatcher/bundle.bat index 3908a6e..90fa284 100644 --- a/dreamcatcher/bundle.bat +++ b/dreamcatcher/bundle.bat @@ -1,2 +1,2 @@ -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 +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/dreamcatcher/pom.xml b/dreamcatcher/pom.xml index 859c17c..2760640 100644 --- a/dreamcatcher/pom.xml +++ b/dreamcatcher/pom.xml @@ -1,70 +1,70 @@ - - 4.0.0 - org.nocturne - dreamcatcher - 1.0-SNAPSHOT - jar - http://code.google.com/p/nocturne/ - 2009-2014 - dreamcatcher - - Catches file changes and reloads classes using special java agent for Nocturne. - - - UTF-8 - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - scm:svn:http://nocturne.googlecode.com/svn/trunk - scm:svn:https://nocturne.googlecode.com/svn/trunk - http://nocturne.googlecode.com/svn/trunk/ - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - 1.7 - 1.7 - UTF-8 - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - attached - - package - - - jar-with-dependencies - - - - - - org.nocturne.dreamcatcher.Dreamcatcher - org.nocturne.dreamcatcher.Dreamcatcher - true - true - - - - - - - - - + + 4.0.0 + org.nocturne + dreamcatcher + 1.0-SNAPSHOT + jar + http://code.google.com/p/nocturne/ + 2009-2014 + dreamcatcher + + Catches file changes and reloads classes using special java agent for Nocturne. + + + UTF-8 + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:svn:http://nocturne.googlecode.com/svn/trunk + scm:svn:https://nocturne.googlecode.com/svn/trunk + http://nocturne.googlecode.com/svn/trunk/ + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + 17 + 17 + UTF-8 + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + attached + + package + + + jar-with-dependencies + + + + + + org.nocturne.dreamcatcher.Dreamcatcher + org.nocturne.dreamcatcher.Dreamcatcher + true + true + + + + + + + + + diff --git a/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/DirectoryListener.java b/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/DirectoryListener.java index fe75af4..7fc8898 100644 --- a/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/DirectoryListener.java +++ b/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/DirectoryListener.java @@ -1,221 +1,221 @@ -package org.nocturne.dreamcatcher; - -import java.io.File; -import java.io.IOException; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * @author Mike Mirzayanov (mirzayanovmr@gmail.com) - */ -public class DirectoryListener { - private final List handlers = new ArrayList<>(); - private final WatchService watcher; - - private final Lock delayedFilesLock = new ReentrantLock(); - private final BlockingQueue delayedFilesQueue = new LinkedBlockingQueue<>(); - private final Set delayedFilesSet = new HashSet<>(); - - DirectoryListener() { - try { - watcher = FileSystems.getDefault().newWatchService(); - } catch (IOException e) { - throw new RuntimeException("Can't create WatchService. Are you sure you are using Java 7+?", e); - } - - Thread delayedFilesQueueListenerThread = new Thread(new DelayedQueueRunnable(), "DirectoryListener.delayedFilesQueueListenerThread"); - delayedFilesQueueListenerThread.setDaemon(true); - delayedFilesQueueListenerThread.start(); - } - - public void addHandler(Handler handler) { - handlers.add(handler); - } - - public void addRootDir(File rootDir) { - try { - listenDirectory(rootDir); - System.out.println("Started to listen directory '" + rootDir + "'."); - } catch (IOException e) { - throw new RuntimeException("Can't listen directory '" + rootDir + "'.", e); - } - } - - public void start() { - Thread innerThread = new Thread(null, new DirectoryWatcherRunnable(), "DirectoryListenerThread"); - innerThread.setDaemon(true); - innerThread.start(); - System.out.println("DirectoryListenerThread has been started."); - } - - private void listenDirectory(File rootDir) throws IOException { - Files.walkFileTree(rootDir.toPath(), new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) - throws IOException { - directory.register( - watcher, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY - ); - return FileVisitResult.CONTINUE; - } - }); - } - - public interface Handler { - void onChange(File file); - } - - private class DelayedQueueRunnable implements Runnable { - private static final long MIN_DELAY_MILLIS = 1000; - - @Override - public void run() { - while (true) { - FileAndTimestamp fileAndTimestamp; - try { - fileAndTimestamp = delayedFilesQueue.poll(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - System.out.println("DelayedQueueRunnable interrupted. " + e); - System.err.println("DelayedQueueRunnable interrupted. " + e); - break; - } - - if (fileAndTimestamp != null) { - long passedTime = System.currentTimeMillis() - fileAndTimestamp.timestamp; - if (passedTime < MIN_DELAY_MILLIS) { - try { - Thread.sleep(MIN_DELAY_MILLIS - passedTime); - } catch (InterruptedException e) { - System.out.println("DelayedQueueRunnable interrupted. " + e); - System.err.println("DelayedQueueRunnable interrupted. " + e); - break; - } - } - - delayedFilesLock.lock(); - try { - delayedFilesSet.remove(fileAndTimestamp); - } finally { - delayedFilesLock.unlock(); - } - - File file = fileAndTimestamp.file; - if (file.isFile()) { - System.out.println("File " + file + " with timestamp " + fileAndTimestamp.timestamp + " processed."); - for (Handler handler : handlers) { - handler.onChange(file); - } - } - } - } - } - } - - private class DirectoryWatcherRunnable implements Runnable { - @Override - public void run() { - while (true) { - WatchKey key; - - try { - key = watcher.take(); - } catch (InterruptedException ignored) { - System.out.println("DirectoryListenerThread has been interrupted."); - break; - } - - for (WatchEvent watchEvent : key.pollEvents()) { - final WatchEvent.Kind kind = watchEvent.kind(); - if (StandardWatchEventKinds.OVERFLOW.equals(kind)) { - continue; - } - - //noinspection unchecked - final WatchEvent pathWatchEvent = (WatchEvent) watchEvent; - final Path path = pathWatchEvent.context(); - final File asFile = new File(String.valueOf(key.watchable()), path.toString()); - - System.out.println("EVENT: " + asFile + " " + kind - + " " + asFile.isFile() + " " + asFile.isDirectory() + " " + asFile.exists()); - - if (asFile.isDirectory() && StandardWatchEventKinds.ENTRY_CREATE.equals(kind)) { - System.out.println("Listen new directory '" + path + "'."); - try { - listenDirectory(asFile); - } catch (IOException e) { - throw new RuntimeException("Can't listen new directory '" + asFile + "'.", e); - } - } - -// if (asFile.isFile()) { -// System.out.println("IsFile: " + asFile); -// for (Handler handler : handlers) { -// handler.onChange(asFile); -// } -// } - - long currentTimeSeconds = (System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1) - 1) / TimeUnit.SECONDS.toMillis(1); - for (int addSeconds = 0; addSeconds <= 2; ++addSeconds) { - FileAndTimestamp fileAndTimestamp = new FileAndTimestamp(asFile, TimeUnit.SECONDS.toMillis(currentTimeSeconds + addSeconds)); - - delayedFilesLock.lock(); - try { - if (!delayedFilesSet.contains(fileAndTimestamp)) { - delayedFilesSet.add(fileAndTimestamp); - delayedFilesQueue.add(fileAndTimestamp); - } - } finally { - delayedFilesLock.unlock(); - } - } - } - - key.reset(); - } - - System.out.println("Finished DirectoryListenerThread."); - } - } - - private static class FileAndTimestamp { - private final File file; - private final long timestamp; - - private FileAndTimestamp(File file, long timestamp) { - this.file = file; - this.timestamp = timestamp; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FileAndTimestamp that = (FileAndTimestamp) o; - - if (timestamp != that.timestamp) return false; - if (file != null ? !file.equals(that.file) : that.file != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = file != null ? file.hashCode() : 0; - result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); - return result; - } - } -} +package org.nocturne.dreamcatcher; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author Mike Mirzayanov (mirzayanovmr@gmail.com) + */ +public class DirectoryListener { + private final List handlers = new ArrayList<>(); + private final WatchService watcher; + + private final Lock delayedFilesLock = new ReentrantLock(); + private final BlockingQueue delayedFilesQueue = new LinkedBlockingQueue<>(); + private final Set delayedFilesSet = new HashSet<>(); + + DirectoryListener() { + try { + watcher = FileSystems.getDefault().newWatchService(); + } catch (IOException e) { + throw new RuntimeException("Can't create WatchService. Are you sure you are using Java 7+?", e); + } + + Thread delayedFilesQueueListenerThread = new Thread(new DelayedQueueRunnable(), "DirectoryListener.delayedFilesQueueListenerThread"); + delayedFilesQueueListenerThread.setDaemon(true); + delayedFilesQueueListenerThread.start(); + } + + public void addHandler(Handler handler) { + handlers.add(handler); + } + + public void addRootDir(File rootDir) { + try { + listenDirectory(rootDir); + System.out.println("Started to listen directory '" + rootDir + "'."); + } catch (IOException e) { + throw new RuntimeException("Can't listen directory '" + rootDir + "'.", e); + } + } + + public void start() { + Thread innerThread = new Thread(null, new DirectoryWatcherRunnable(), "DirectoryListenerThread"); + innerThread.setDaemon(true); + innerThread.start(); + System.out.println("DirectoryListenerThread has been started."); + } + + private void listenDirectory(File rootDir) throws IOException { + Files.walkFileTree(rootDir.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attributes) + throws IOException { + directory.register( + watcher, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY + ); + return FileVisitResult.CONTINUE; + } + }); + } + + public interface Handler { + void onChange(File file); + } + + private class DelayedQueueRunnable implements Runnable { + private static final long MIN_DELAY_MILLIS = 1000; + + @Override + public void run() { + while (true) { + FileAndTimestamp fileAndTimestamp; + try { + fileAndTimestamp = delayedFilesQueue.poll(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("DelayedQueueRunnable interrupted. " + e); + System.err.println("DelayedQueueRunnable interrupted. " + e); + break; + } + + if (fileAndTimestamp != null) { + long passedTime = System.currentTimeMillis() - fileAndTimestamp.timestamp; + if (passedTime < MIN_DELAY_MILLIS) { + try { + Thread.sleep(MIN_DELAY_MILLIS - passedTime); + } catch (InterruptedException e) { + System.out.println("DelayedQueueRunnable interrupted. " + e); + System.err.println("DelayedQueueRunnable interrupted. " + e); + break; + } + } + + delayedFilesLock.lock(); + try { + delayedFilesSet.remove(fileAndTimestamp); + } finally { + delayedFilesLock.unlock(); + } + + File file = fileAndTimestamp.file; + if (file.isFile()) { + System.out.println("File " + file + " with timestamp " + fileAndTimestamp.timestamp + " processed."); + for (Handler handler : handlers) { + handler.onChange(file); + } + } + } + } + } + } + + private class DirectoryWatcherRunnable implements Runnable { + @Override + public void run() { + while (true) { + WatchKey key; + + try { + key = watcher.take(); + } catch (InterruptedException ignored) { + System.out.println("DirectoryListenerThread has been interrupted."); + break; + } + + for (WatchEvent watchEvent : key.pollEvents()) { + final WatchEvent.Kind kind = watchEvent.kind(); + if (StandardWatchEventKinds.OVERFLOW.equals(kind)) { + continue; + } + + //noinspection unchecked + final WatchEvent pathWatchEvent = (WatchEvent) watchEvent; + final Path path = pathWatchEvent.context(); + final File asFile = new File(String.valueOf(key.watchable()), path.toString()); + + System.out.println("EVENT: " + asFile + " " + kind + + " " + asFile.isFile() + " " + asFile.isDirectory() + " " + asFile.exists()); + + if (asFile.isDirectory() && StandardWatchEventKinds.ENTRY_CREATE.equals(kind)) { + System.out.println("Listen new directory '" + path + "'."); + try { + listenDirectory(asFile); + } catch (IOException e) { + throw new RuntimeException("Can't listen new directory '" + asFile + "'.", e); + } + } + +// if (asFile.isFile()) { +// System.out.println("IsFile: " + asFile); +// for (Handler handler : handlers) { +// handler.onChange(asFile); +// } +// } + + long currentTimeSeconds = (System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1) - 1) / TimeUnit.SECONDS.toMillis(1); + for (int addSeconds = 0; addSeconds <= 2; ++addSeconds) { + FileAndTimestamp fileAndTimestamp = new FileAndTimestamp(asFile, TimeUnit.SECONDS.toMillis(currentTimeSeconds + addSeconds)); + + delayedFilesLock.lock(); + try { + if (!delayedFilesSet.contains(fileAndTimestamp)) { + delayedFilesSet.add(fileAndTimestamp); + delayedFilesQueue.add(fileAndTimestamp); + } + } finally { + delayedFilesLock.unlock(); + } + } + } + + key.reset(); + } + + System.out.println("Finished DirectoryListenerThread."); + } + } + + private static class FileAndTimestamp { + private final File file; + private final long timestamp; + + private FileAndTimestamp(File file, long timestamp) { + this.file = file; + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FileAndTimestamp that = (FileAndTimestamp) o; + + if (timestamp != that.timestamp) return false; + if (file != null ? !file.equals(that.file) : that.file != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = file != null ? file.hashCode() : 0; + result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); + return result; + } + } +} diff --git a/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/Dreamcatcher.java b/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/Dreamcatcher.java index b535d4d..e036202 100644 --- a/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/Dreamcatcher.java +++ b/dreamcatcher/src/main/java/org/nocturne/dreamcatcher/Dreamcatcher.java @@ -1,265 +1,265 @@ -package org.nocturne.dreamcatcher; - -import java.io.*; -import java.lang.instrument.ClassDefinition; -import java.lang.instrument.Instrumentation; -import java.util.*; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * @author Mike Mirzayanov (mirzayanovmr@gmail.com) - */ -public class Dreamcatcher implements DirectoryListener.Handler { - private static final List dreamcatchers = Collections.synchronizedList(new ArrayList()); - private static final String NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS = "nocturne.unused-reloading-class-loaders"; - private static final String DREAMCATCHER_LISTEN_DIRECTORIES = "dreamcatcher.listen-directories"; - private static final String CLASS_EXT = ".class"; - private static final String RELOADING_CLASS_LOADER_NAME_PREFIX = "org.nocturne.main.ReloadingClassLoader.DelegationClassLoader"; - - private final Set listenDirectories = new HashSet<>(); - private final Set listenDirectoryFiles = new HashSet<>(); - private final Lock listenDirectoriesLock = new ReentrantLock(); - private final Instrumentation inst; - private final DirectoryListener directoryListener = new DirectoryListener(); - - private volatile int lastDreamcatcherListenDirectoriesSize = 0; - - public Dreamcatcher(Instrumentation inst, String args) { - this.inst = inst; - - directoryListener.addHandler(this); - directoryListener.start(); - - startDirectoryListenerByArgs(args); - - Thread rescanForNewListenDirectoriesThread = new Thread(null, new Runnable() { - @Override - public void run() { - while (true) { - try { - listenMoreDirectoriesIfNeeded(); - } catch (Exception e) { - e.printStackTrace(); - } - - try { - Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - } catch (InterruptedException e) { - break; - } - } - } - }, "Dreamcatcher.rescanForNewListenDirectoriesThread"); - rescanForNewListenDirectoriesThread.setDaemon(true); - rescanForNewListenDirectoriesThread.start(); - } - - private static Set getDreamcatcherListenDirectories() { - //noinspection unchecked - Set result = (Set) System.getProperties().get(DREAMCATCHER_LISTEN_DIRECTORIES); - - if (result == null) { - System.getProperties().put(DREAMCATCHER_LISTEN_DIRECTORIES, new ConcurrentSkipListSet()); - //noinspection unchecked - result = (Set) System.getProperties().get(DREAMCATCHER_LISTEN_DIRECTORIES); - } - - return result; - } - - private void listenDirectory(String directory) { - listenDirectoriesLock.lock(); - - try { - if (!listenDirectories.contains(directory)) { - File dir = new File(directory); - if (!dir.exists() || !dir.isDirectory()) { - throw new RuntimeException("Argument '" - + directory - + "' expected to be directory to listen, but it is not a directory."); - } - directoryListener.addRootDir(dir); - listenDirectoryFiles.add(dir); - listenDirectories.add(directory); - getDreamcatcherListenDirectories().add(directory); - System.out.println("Dreamcatcher listen directory '" + directory + "'."); - } - } finally { - listenDirectoriesLock.unlock(); - } - } - - private void startDirectoryListenerByArgs(String args) { - if (args != null && args.length() > 0) { - for (String token : args.split(";")) { - String directory = token.trim(); - if (!directory.isEmpty()) { - listenDirectory(directory); - } - } - } - } - - /** - * Called when the agent is initialized via command line - */ - public static void premain(String args, Instrumentation inst) { - System.setProperty("dreamcatcher.loaded", "true"); - System.out.println("Dreamcatcher premain (" + args + ")."); - initialize(args, inst); - } - - /** - * Called when the agent is initialized after the jvm startup - */ - @SuppressWarnings("UnusedDeclaration") - public static void agentmain(String args, Instrumentation inst) { - System.setProperty("dreamcatcher.loaded", "true"); - System.out.println("Dreamcatcher agentmain (" + args + ")."); - initialize(args, inst); - } - - private static void initialize(String args, Instrumentation inst) { - if (!getDreamcatcherListenDirectories().isEmpty()) { - throw new RuntimeException("Expected empty dreamcatcher listen directories set."); - } - - Dreamcatcher dreamcatcher = new Dreamcatcher(inst, args); - dreamcatchers.add(dreamcatcher); - } - - @Override - public void onChange(File file) { - if (file.getName().endsWith(CLASS_EXT)) { - redefineClass(file); - } - } - - private static boolean equalFiles(File fileA, File fileB) { - try { - return fileA.getCanonicalPath().equals(fileB.getCanonicalPath()) - && fileA.getAbsolutePath().equals(fileB.getAbsolutePath()); - } catch (IOException e) { - throw new RuntimeException("Unexpected IOException in getCanonicalPath.", e); - } - } - - protected void redefineClass(File classFile) { - listenMoreDirectoriesIfNeeded(); - - if (!System.getProperties().containsKey(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS)) { - System.getProperties().put(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS, new HashSet()); - } - - //noinspection unchecked - Set unusedReloadingClassLoaders - = (Set) System.getProperties().get(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS); - - List pathElements = new ArrayList<>(); - boolean top = false; - File clazz = classFile; - - while (!top && clazz != null) { - pathElements.add(clazz.getName()); - clazz = clazz.getParentFile(); - - for (File listenDirectory : listenDirectoryFiles) { - if (equalFiles(listenDirectory, clazz)) { - top = true; - break; - } - } - } - - if (!top) { - return; - } - - Collections.reverse(pathElements); - - StringBuilder nameStringBuilder = new StringBuilder(); - for (String pathElement : pathElements) { - if (nameStringBuilder.length() > 0) { - nameStringBuilder.append('.'); - } - nameStringBuilder.append(pathElement); - } - - String name = nameStringBuilder.toString(); - if (name.endsWith(CLASS_EXT)) { - name = name.substring(0, name.length() - CLASS_EXT.length()); - } else { - return; - } - - System.out.println("name=" + name); - - Class[] loadedClasses = inst.getAllLoadedClasses(); - for (Class loadedClass : loadedClasses) { - if (loadedClass.getClassLoader() == null) { - continue; - } - - if (unusedReloadingClassLoaders.contains(loadedClass.getClassLoader())) { - continue; - } - - //System.out.println(loadedClass.getClassLoader().getClass().toString() + " " + loadedClass.getClassLoader().getClass().getName() + " " + loadedClass.getClassLoader().getClass().getSimpleName() + " " + loadedClass.getClassLoader().getClass().getCanonicalName()); - - if (!loadedClass.getClassLoader().getClass().getCanonicalName().startsWith(RELOADING_CLASS_LOADER_NAME_PREFIX)) { - continue; - } - - if (loadedClass.getName().equals(name)) { - try { - System.out.println("= Ready to redefine " + loadedClass.getName() + "@" + loadedClass.getClassLoader() + " with " + classFile); - ClassDefinition definition = new ClassDefinition(loadedClass, toByteArray(new FileInputStream(classFile))); - //noinspection RedundantArrayCreation - inst.redefineClasses(new ClassDefinition[]{definition}); - System.out.println("Redefined " + loadedClass.getName() + " with " + classFile); - } catch (IOException e) { - System.out.println(e.getClass() + " " + e.getMessage() + " " + name + " " + loadedClass); - // No operations. - } catch (Throwable e) { - System.out.println("Can't redefine " + loadedClass); - e.printStackTrace(); - System.setProperty("dreamcatcher.can-not-redefine-class", "true"); - } - } - } - } - - private void listenMoreDirectoriesIfNeeded() { - if (lastDreamcatcherListenDirectoriesSize < getDreamcatcherListenDirectories().size()) { - listenDirectoriesLock.lock(); - - try { - List directories = new ArrayList<>(getDreamcatcherListenDirectories()); - for (String directory : directories) { - listenDirectory(directory); - } - lastDreamcatcherListenDirectoriesSize = Math.max(lastDreamcatcherListenDirectoriesSize, directories.size()); - } finally { - listenDirectoriesLock.unlock(); - } - } - } - - private static byte[] toByteArray(InputStream inputStream) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - byte[] buffer = new byte[65536]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - if (bytesRead > 0) { - byteArrayOutputStream.write(buffer, 0, bytesRead); - } - } - - inputStream.close(); - return byteArrayOutputStream.toByteArray(); - } -} +package org.nocturne.dreamcatcher; + +import java.io.*; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; +import java.util.*; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author Mike Mirzayanov (mirzayanovmr@gmail.com) + */ +public class Dreamcatcher implements DirectoryListener.Handler { + private static final List dreamcatchers = Collections.synchronizedList(new ArrayList()); + private static final String NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS = "nocturne.unused-reloading-class-loaders"; + private static final String DREAMCATCHER_LISTEN_DIRECTORIES = "dreamcatcher.listen-directories"; + private static final String CLASS_EXT = ".class"; + private static final String RELOADING_CLASS_LOADER_NAME_PREFIX = "org.nocturne.main.ReloadingClassLoader.DelegationClassLoader"; + + private final Set listenDirectories = new HashSet<>(); + private final Set listenDirectoryFiles = new HashSet<>(); + private final Lock listenDirectoriesLock = new ReentrantLock(); + private final Instrumentation inst; + private final DirectoryListener directoryListener = new DirectoryListener(); + + private volatile int lastDreamcatcherListenDirectoriesSize = 0; + + public Dreamcatcher(Instrumentation inst, String args) { + this.inst = inst; + + directoryListener.addHandler(this); + directoryListener.start(); + + startDirectoryListenerByArgs(args); + + Thread rescanForNewListenDirectoriesThread = new Thread(null, new Runnable() { + @Override + public void run() { + while (true) { + try { + listenMoreDirectoriesIfNeeded(); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + Thread.sleep(TimeUnit.SECONDS.toMillis(1)); + } catch (InterruptedException e) { + break; + } + } + } + }, "Dreamcatcher.rescanForNewListenDirectoriesThread"); + rescanForNewListenDirectoriesThread.setDaemon(true); + rescanForNewListenDirectoriesThread.start(); + } + + private static Set getDreamcatcherListenDirectories() { + //noinspection unchecked + Set result = (Set) System.getProperties().get(DREAMCATCHER_LISTEN_DIRECTORIES); + + if (result == null) { + System.getProperties().put(DREAMCATCHER_LISTEN_DIRECTORIES, new ConcurrentSkipListSet()); + //noinspection unchecked + result = (Set) System.getProperties().get(DREAMCATCHER_LISTEN_DIRECTORIES); + } + + return result; + } + + private void listenDirectory(String directory) { + listenDirectoriesLock.lock(); + + try { + if (!listenDirectories.contains(directory)) { + File dir = new File(directory); + if (!dir.exists() || !dir.isDirectory()) { + throw new RuntimeException("Argument '" + + directory + + "' expected to be directory to listen, but it is not a directory."); + } + directoryListener.addRootDir(dir); + listenDirectoryFiles.add(dir); + listenDirectories.add(directory); + getDreamcatcherListenDirectories().add(directory); + System.out.println("Dreamcatcher listen directory '" + directory + "'."); + } + } finally { + listenDirectoriesLock.unlock(); + } + } + + private void startDirectoryListenerByArgs(String args) { + if (args != null && args.length() > 0) { + for (String token : args.split(";")) { + String directory = token.trim(); + if (!directory.isEmpty()) { + listenDirectory(directory); + } + } + } + } + + /** + * Called when the agent is initialized via command line + */ + public static void premain(String args, Instrumentation inst) { + System.setProperty("dreamcatcher.loaded", "true"); + System.out.println("Dreamcatcher premain (" + args + ")."); + initialize(args, inst); + } + + /** + * Called when the agent is initialized after the jvm startup + */ + @SuppressWarnings("UnusedDeclaration") + public static void agentmain(String args, Instrumentation inst) { + System.setProperty("dreamcatcher.loaded", "true"); + System.out.println("Dreamcatcher agentmain (" + args + ")."); + initialize(args, inst); + } + + private static void initialize(String args, Instrumentation inst) { + if (!getDreamcatcherListenDirectories().isEmpty()) { + throw new RuntimeException("Expected empty dreamcatcher listen directories set."); + } + + Dreamcatcher dreamcatcher = new Dreamcatcher(inst, args); + dreamcatchers.add(dreamcatcher); + } + + @Override + public void onChange(File file) { + if (file.getName().endsWith(CLASS_EXT)) { + redefineClass(file); + } + } + + private static boolean equalFiles(File fileA, File fileB) { + try { + return fileA.getCanonicalPath().equals(fileB.getCanonicalPath()) + && fileA.getAbsolutePath().equals(fileB.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Unexpected IOException in getCanonicalPath.", e); + } + } + + protected void redefineClass(File classFile) { + listenMoreDirectoriesIfNeeded(); + + if (!System.getProperties().containsKey(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS)) { + System.getProperties().put(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS, new HashSet()); + } + + //noinspection unchecked + Set unusedReloadingClassLoaders + = (Set) System.getProperties().get(NOCTURNE_UNUSED_RELOADING_CLASS_LOADERS); + + List pathElements = new ArrayList<>(); + boolean top = false; + File clazz = classFile; + + while (!top && clazz != null) { + pathElements.add(clazz.getName()); + clazz = clazz.getParentFile(); + + for (File listenDirectory : listenDirectoryFiles) { + if (equalFiles(listenDirectory, clazz)) { + top = true; + break; + } + } + } + + if (!top) { + return; + } + + Collections.reverse(pathElements); + + StringBuilder nameStringBuilder = new StringBuilder(); + for (String pathElement : pathElements) { + if (nameStringBuilder.length() > 0) { + nameStringBuilder.append('.'); + } + nameStringBuilder.append(pathElement); + } + + String name = nameStringBuilder.toString(); + if (name.endsWith(CLASS_EXT)) { + name = name.substring(0, name.length() - CLASS_EXT.length()); + } else { + return; + } + + System.out.println("name=" + name); + + Class[] loadedClasses = inst.getAllLoadedClasses(); + for (Class loadedClass : loadedClasses) { + if (loadedClass.getClassLoader() == null) { + continue; + } + + if (unusedReloadingClassLoaders.contains(loadedClass.getClassLoader())) { + continue; + } + + //System.out.println(loadedClass.getClassLoader().getClass().toString() + " " + loadedClass.getClassLoader().getClass().getName() + " " + loadedClass.getClassLoader().getClass().getSimpleName() + " " + loadedClass.getClassLoader().getClass().getCanonicalName()); + + if (!loadedClass.getClassLoader().getClass().getCanonicalName().startsWith(RELOADING_CLASS_LOADER_NAME_PREFIX)) { + continue; + } + + if (loadedClass.getName().equals(name)) { + try { + System.out.println("= Ready to redefine " + loadedClass.getName() + "@" + loadedClass.getClassLoader() + " with " + classFile); + ClassDefinition definition = new ClassDefinition(loadedClass, toByteArray(new FileInputStream(classFile))); + //noinspection RedundantArrayCreation + inst.redefineClasses(new ClassDefinition[]{definition}); + System.out.println("Redefined " + loadedClass.getName() + " with " + classFile); + } catch (IOException e) { + System.out.println(e.getClass() + " " + e.getMessage() + " " + name + " " + loadedClass); + // No operations. + } catch (Throwable e) { + System.out.println("Can't redefine " + loadedClass); + e.printStackTrace(); + System.setProperty("dreamcatcher.can-not-redefine-class", "true"); + } + } + } + } + + private void listenMoreDirectoriesIfNeeded() { + if (lastDreamcatcherListenDirectoriesSize < getDreamcatcherListenDirectories().size()) { + listenDirectoriesLock.lock(); + + try { + List directories = new ArrayList<>(getDreamcatcherListenDirectories()); + for (String directory : directories) { + listenDirectory(directory); + } + lastDreamcatcherListenDirectoriesSize = Math.max(lastDreamcatcherListenDirectoriesSize, directories.size()); + } finally { + listenDirectoriesLock.unlock(); + } + } + } + + private static byte[] toByteArray(InputStream inputStream) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + byte[] buffer = new byte[65536]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + if (bytesRead > 0) { + byteArrayOutputStream.write(buffer, 0, bytesRead); + } + } + + inputStream.close(); + return byteArrayOutputStream.toByteArray(); + } +} diff --git a/repo/org/nocturne/nocturne/1.0.0-SNAPSHOT/resolver-status.properties b/repo/org/nocturne/nocturne/1.0.0-SNAPSHOT/resolver-status.properties index 1cfe3b2..70ce7ba 100644 --- a/repo/org/nocturne/nocturne/1.0.0-SNAPSHOT/resolver-status.properties +++ b/repo/org/nocturne/nocturne/1.0.0-SNAPSHOT/resolver-status.properties @@ -1,8 +1,8 @@ -#Last modified on: Tue Oct 19 12:27:28 MSD 2010 -#Tue Oct 19 12:27:28 MSD 2010 -codegame.sgu.ru-snapshots.maven-metadata-codegame.sgu.ru-snapshots.xml.lastUpdated=1286186332308 -codegame.sgu.ru-releases.maven-metadata-codegame.sgu.ru-releases.xml.lastUpdated=1286186332344 -oss.sonatype.org-releases.maven-metadata-oss.sonatype.org-releases.xml.lastUpdated=1287476848121 -codegame.sgu.ru.maven-metadata-codegame.sgu.ru.xml.lastUpdated=1261374716904 -codecenter.sgu.ru-snapshots.maven-metadata-codecenter.sgu.ru-snapshots.xml.lastUpdated=1286535734653 -codecenter.sgu.ru-releases.maven-metadata-codecenter.sgu.ru-releases.xml.lastUpdated=1286535734666 +#Last modified on: Tue Oct 19 12:27:28 MSD 2010 +#Tue Oct 19 12:27:28 MSD 2010 +codegame.sgu.ru-snapshots.maven-metadata-codegame.sgu.ru-snapshots.xml.lastUpdated=1286186332308 +codegame.sgu.ru-releases.maven-metadata-codegame.sgu.ru-releases.xml.lastUpdated=1286186332344 +oss.sonatype.org-releases.maven-metadata-oss.sonatype.org-releases.xml.lastUpdated=1287476848121 +codegame.sgu.ru.maven-metadata-codegame.sgu.ru.xml.lastUpdated=1261374716904 +codecenter.sgu.ru-snapshots.maven-metadata-codecenter.sgu.ru-snapshots.xml.lastUpdated=1286535734653 +codecenter.sgu.ru-releases.maven-metadata-codecenter.sgu.ru-releases.xml.lastUpdated=1286535734666 diff --git a/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.jar.lastUpdated b/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.jar.lastUpdated index 1302c32..ac79c22 100644 --- a/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.jar.lastUpdated +++ b/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.jar.lastUpdated @@ -1,3 +1,3 @@ -#Last modified on: Tue Oct 12 10:03:04 MSD 2010 -#Tue Oct 12 10:03:04 MSD 2010 -nocturne=1286863384240 +#Last modified on: Tue Oct 12 10:03:04 MSD 2010 +#Tue Oct 12 10:03:04 MSD 2010 +nocturne=1286863384240 diff --git a/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.pom.lastUpdated b/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.pom.lastUpdated index 352fa79..fce889a 100644 --- a/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.pom.lastUpdated +++ b/repo/org/nocturne/nocturne/1.0.1-SNAPSHOT/nocturne-1.0.1-SNAPSHOT.pom.lastUpdated @@ -1,3 +1,3 @@ -#Last modified on: Tue Oct 12 10:03:03 MSD 2010 -#Tue Oct 12 10:03:03 MSD 2010 -nocturne=1286863383927 +#Last modified on: Tue Oct 12 10:03:03 MSD 2010 +#Tue Oct 12 10:03:03 MSD 2010 +nocturne=1286863383927 diff --git a/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/_remote.repositories b/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/_remote.repositories index 08df196..006ac35 100644 --- a/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/_remote.repositories +++ b/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/_remote.repositories @@ -1,6 +1,6 @@ -#NOTE: This is an Aether internal implementation file, its format can be changed without prior notice. -#Wed Apr 05 15:05:51 SAMT 2017 -nocturne-1.3.3-SNAPSHOT-javadoc.jar>= -nocturne-1.3.3-SNAPSHOT.pom>= -nocturne-1.3.3-SNAPSHOT-sources.jar>= -nocturne-1.3.3-SNAPSHOT.jar>= +#NOTE: This is an Aether internal implementation file, its format can be changed without prior notice. +#Wed Apr 05 15:05:51 SAMT 2017 +nocturne-1.3.3-SNAPSHOT-javadoc.jar>= +nocturne-1.3.3-SNAPSHOT.pom>= +nocturne-1.3.3-SNAPSHOT-sources.jar>= +nocturne-1.3.3-SNAPSHOT.jar>= diff --git a/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/nocturne-1.3.3-SNAPSHOT.pom b/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/nocturne-1.3.3-SNAPSHOT.pom index 76037b4..fb46749 100644 --- a/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/nocturne-1.3.3-SNAPSHOT.pom +++ b/repo/org/nocturne/nocturne/1.3.3-SNAPSHOT/nocturne-1.3.3-SNAPSHOT.pom @@ -1,237 +1,237 @@ - - - 4.0.0 - org.nocturne - nocturne - 1.3.3-SNAPSHOT - jar - - https://github.com/Codeforces/nocturne/ - 2009-2016 - 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 - - - - - - - Maxim Shipko - sladethe@gmail.com - - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - 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.23 - - - cglib - cglib - 3.0 - - - com.google.code.findbugs - annotations - 3.0.1u2 - compile - - - org.ow2.asm - asm - 4.0 - - - org.ow2.asm - asm-tree - 4.0 - - - org.ow2.asm - asm-analysis - 4.0 - - - org.ow2.asm - asm-util - 4.0 - - - org.apache.commons - commons-lang3 - 3.5 - - - commons-io - commons-io - 2.5 - - - commons-fileupload - commons-fileupload - 1.3.2 - - - com.google.code.gson - gson - 2.8.0 - - - com.google.guava - guava - 21.0 - - - com.google.inject - guice - 4.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.24 - - - org.slf4j - slf4j-log4j12 - 1.7.24 - - - com.fasterxml.jackson.core - jackson-databind - 2.8.7 - - - com.fasterxml.jackson.core - jackson-annotations - 2.8.7 - - - com.fasterxml.jackson.core - jackson-core - 2.8.7 - - - com.maxmind.geoip2 - geoip2 - 2.8.1 - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - - - javax.servlet - servlet-api - 2.5 - provided - - - org.jetbrains - annotations - 15.0 - - - junit - junit - - 4.11 - test - - - - - nocturne - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - 1.8 - 1.8 - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - UTF-8 - - - - - - src/main/resources - true - - - src/main/files - false - - - - - - UTF-8 - - + + + 4.0.0 + org.nocturne + nocturne + 1.3.3-SNAPSHOT + jar + + https://github.com/Codeforces/nocturne/ + 2009-2016 + 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 + + + + + + + Maxim Shipko + sladethe@gmail.com + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + 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.23 + + + cglib + cglib + 3.0 + + + com.google.code.findbugs + annotations + 3.0.1u2 + compile + + + org.ow2.asm + asm + 4.0 + + + org.ow2.asm + asm-tree + 4.0 + + + org.ow2.asm + asm-analysis + 4.0 + + + org.ow2.asm + asm-util + 4.0 + + + org.apache.commons + commons-lang3 + 3.5 + + + commons-io + commons-io + 2.5 + + + commons-fileupload + commons-fileupload + 1.3.2 + + + com.google.code.gson + gson + 2.8.0 + + + com.google.guava + guava + 21.0 + + + com.google.inject + guice + 4.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.24 + + + org.slf4j + slf4j-log4j12 + 1.7.24 + + + com.fasterxml.jackson.core + jackson-databind + 2.8.7 + + + com.fasterxml.jackson.core + jackson-annotations + 2.8.7 + + + com.fasterxml.jackson.core + jackson-core + 2.8.7 + + + com.maxmind.geoip2 + geoip2 + 2.8.1 + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + + + javax.servlet + servlet-api + 2.5 + provided + + + org.jetbrains + annotations + 15.0 + + + junit + junit + + 4.11 + test + + + + + nocturne + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + UTF-8 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + UTF-8 + + + + + + src/main/resources + true + + + src/main/files + false + + + + + + UTF-8 + + diff --git a/repo/org/nocturne/nocturne/maven-metadata-local.xml b/repo/org/nocturne/nocturne/maven-metadata-local.xml index 4df967c..f516869 100644 --- a/repo/org/nocturne/nocturne/maven-metadata-local.xml +++ b/repo/org/nocturne/nocturne/maven-metadata-local.xml @@ -1,22 +1,22 @@ - - - org.nocturne - nocturne - 1.3.3-SNAPSHOT - - - 1.1.1-SNAPSHOT - 1.2.0-SNAPSHOT - 1.2.1-SNAPSHOT - 1.2.2-SNAPSHOT - 1.2.3-SNAPSHOT - 1.2.4-SNAPSHOT - 1.2.5-SNAPSHOT - 1.2.6-SNAPSHOT - 1.2.7-SNAPSHOT - 1.2.8-SNAPSHOT - 1.3.3-SNAPSHOT - - 20170405110551 - - + + + org.nocturne + nocturne + 1.3.3-SNAPSHOT + + + 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT + 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.3-SNAPSHOT + 1.2.4-SNAPSHOT + 1.2.5-SNAPSHOT + 1.2.6-SNAPSHOT + 1.2.7-SNAPSHOT + 1.2.8-SNAPSHOT + 1.3.3-SNAPSHOT + + 20170405110551 + + diff --git a/samples/bloggy/pom.xml b/samples/bloggy/pom.xml index 33e083a..ed495ba 100644 --- a/samples/bloggy/pom.xml +++ b/samples/bloggy/pom.xml @@ -9,8 +9,8 @@ UTF-8 - 1.8 - 1.8 + 17 + 17 @@ -72,10 +72,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 - 1.8 - 1.8 + 17 + 17 UTF-8 -g @@ -83,7 +83,7 @@ org.apache.maven.plugins maven-war-plugin - 3.3.2 + 3.5.1 diff --git a/samples/helloworld/pom.xml b/samples/helloworld/pom.xml index ffc95e1..1514fda 100644 --- a/samples/helloworld/pom.xml +++ b/samples/helloworld/pom.xml @@ -32,10 +32,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.14.1 - 1.8 - 1.8 + 17 + 17 UTF-8 diff --git a/samples/helloworld/profile.properties b/samples/helloworld/profile.properties index b0efe32..f9703da 100644 --- a/samples/helloworld/profile.properties +++ b/samples/helloworld/profile.properties @@ -1,4 +1,4 @@ -development.home=P:/nocturne/samples/helloworld -logging.path=${development.home}/helloworld.log -nocturne.debug=true -nocturne.template-paths=${development.home}/src/main/webapp/WEB-INF/templates +development.home=P:/nocturne/samples/helloworld +logging.path=${development.home}/helloworld.log +nocturne.debug=true +nocturne.template-paths=${development.home}/src/main/webapp/WEB-INF/templates diff --git a/samples/helloworld/src/main/java/helloworld/ApplicationModule.java b/samples/helloworld/src/main/java/helloworld/ApplicationModule.java index 1fa9c43..d6448ba 100644 --- a/samples/helloworld/src/main/java/helloworld/ApplicationModule.java +++ b/samples/helloworld/src/main/java/helloworld/ApplicationModule.java @@ -1,18 +1,18 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ - -package helloworld; - -import com.google.inject.Binder; -import com.google.inject.Module; - -/** - * @author Mike Mirzayanov - */ -public class ApplicationModule implements Module { - @Override - public void configure(Binder binder) { - // No IoC binding. - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ + +package helloworld; + +import com.google.inject.Binder; +import com.google.inject.Module; + +/** + * @author Mike Mirzayanov + */ +public class ApplicationModule implements Module { + @Override + public void configure(Binder binder) { + // No IoC binding. + } +} diff --git a/samples/helloworld/src/main/java/helloworld/ApplicationRequestRouter.java b/samples/helloworld/src/main/java/helloworld/ApplicationRequestRouter.java index eb38149..55a8ac0 100644 --- a/samples/helloworld/src/main/java/helloworld/ApplicationRequestRouter.java +++ b/samples/helloworld/src/main/java/helloworld/ApplicationRequestRouter.java @@ -1,18 +1,18 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ - -package helloworld; - -import helloworld.page.IndexPage; -import org.nocturne.main.LinkedRequestRouter; -import org.nocturne.link.Links; - -/** - * @author Mike Mirzayanov - */ -public class ApplicationRequestRouter extends LinkedRequestRouter { - static { - Links.add(IndexPage.class); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ + +package helloworld; + +import helloworld.page.IndexPage; +import org.nocturne.main.LinkedRequestRouter; +import org.nocturne.link.Links; + +/** + * @author Mike Mirzayanov + */ +public class ApplicationRequestRouter extends LinkedRequestRouter { + static { + Links.add(IndexPage.class); + } +} diff --git a/samples/helloworld/src/main/java/helloworld/page/IndexPage.java b/samples/helloworld/src/main/java/helloworld/page/IndexPage.java index 222e82e..cdcaa7c 100644 --- a/samples/helloworld/src/main/java/helloworld/page/IndexPage.java +++ b/samples/helloworld/src/main/java/helloworld/page/IndexPage.java @@ -1,17 +1,17 @@ -/* - * Copyright 2009 Mike Mirzayanov - */ - -package helloworld.page; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** @author Mike Mirzayanov */ -@Link("") -public class IndexPage extends Page { - @Override - public void action() { - put("message", "Hello, world"); - } -} +/* + * Copyright 2009 Mike Mirzayanov + */ + +package helloworld.page; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** @author Mike Mirzayanov */ +@Link("") +public class IndexPage extends Page { + @Override + public void action() { + put("message", "Hello, world"); + } +} diff --git a/samples/helloworld/src/main/resources/captions_en.properties b/samples/helloworld/src/main/resources/captions_en.properties index 6d45439..2760072 100644 --- a/samples/helloworld/src/main/resources/captions_en.properties +++ b/samples/helloworld/src/main/resources/captions_en.properties @@ -1 +1 @@ -Message=Message +Message=Message diff --git a/samples/helloworld/src/main/resources/log4j.properties b/samples/helloworld/src/main/resources/log4j.properties index bd53163..2af1583 100644 --- a/samples/helloworld/src/main/resources/log4j.properties +++ b/samples/helloworld/src/main/resources/log4j.properties @@ -1,13 +1,13 @@ -log4j.rootCategory=INFO,stdout,log_file -log4j.logger.helloworld=DEBUG - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m%n - -log4j.appender.log_file=org.apache.log4j.RollingFileAppender -log4j.appender.log_file.File=${logging.path} -log4j.appender.log_file.MaxFileSize=10MB -log4j.appender.log_file.MaxBackupIndex=1 -log4j.appender.log_file.layout=org.apache.log4j.PatternLayout -log4j.appender.log_file.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m (%F:%L, %t)%n +log4j.rootCategory=INFO,stdout,log_file +log4j.logger.helloworld=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m%n + +log4j.appender.log_file=org.apache.log4j.RollingFileAppender +log4j.appender.log_file.File=${logging.path} +log4j.appender.log_file.MaxFileSize=10MB +log4j.appender.log_file.MaxBackupIndex=1 +log4j.appender.log_file.layout=org.apache.log4j.PatternLayout +log4j.appender.log_file.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m (%F:%L, %t)%n diff --git a/samples/helloworld/src/main/resources/nocturne.properties b/samples/helloworld/src/main/resources/nocturne.properties index 1feb84b..8625808 100644 --- a/samples/helloworld/src/main/resources/nocturne.properties +++ b/samples/helloworld/src/main/resources/nocturne.properties @@ -1,7 +1,7 @@ -nocturne.debug=${nocturne.debug} -nocturne.template-paths=${nocturne.template-paths} -nocturne.request-router=helloworld.ApplicationRequestRouter -nocturne.guice-module-class-name=helloworld.ApplicationModule -nocturne.class-reloading-packages=helloworld -nocturne.reloading-class-paths=${development.home}/target/classes -nocturne.debug-captions-dir=${development.home}/src/main/resources +nocturne.debug=${nocturne.debug} +nocturne.template-paths=${nocturne.template-paths} +nocturne.request-router=helloworld.ApplicationRequestRouter +nocturne.guice-module-class-name=helloworld.ApplicationModule +nocturne.class-reloading-packages=helloworld +nocturne.reloading-class-paths=${development.home}/target/classes +nocturne.debug-captions-dir=${development.home}/src/main/resources diff --git a/samples/helloworld/src/main/webapp/WEB-INF/templates/IndexPage.ftl b/samples/helloworld/src/main/webapp/WEB-INF/templates/IndexPage.ftl index df24f3c..38555bd 100644 --- a/samples/helloworld/src/main/webapp/WEB-INF/templates/IndexPage.ftl +++ b/samples/helloworld/src/main/webapp/WEB-INF/templates/IndexPage.ftl @@ -1,16 +1,16 @@ -<#-- @ftlvariable name="message" type="java.lang.String" --> -<#-- @ftlvariable name="home" type="java.lang.String" --> -<#setting url_escaping_charset='UTF-8'> - - - - - - - Nocturne hello world - - - - {{Message}}: ${message} - - +<#-- @ftlvariable name="message" type="java.lang.String" --> +<#-- @ftlvariable name="home" type="java.lang.String" --> +<#setting url_escaping_charset='UTF-8'> + + + + + + + Nocturne hello world + + + + {{Message}}: ${message} + + diff --git a/samples/helloworld/src/main/webapp/css/clear.css b/samples/helloworld/src/main/webapp/css/clear.css index 24865a1..6ff4113 100644 --- a/samples/helloworld/src/main/webapp/css/clear.css +++ b/samples/helloworld/src/main/webapp/css/clear.css @@ -1,15 +1,15 @@ -@charset "utf-8"; -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;} ol,ul{list-style:none;} caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} q:before,q:after{content:'';} abbr,acronym {border:0;} -a:link { color: #0000cc; } -body { background: #fff; margin: 3px 8px;} -body, select, input { font-family: verdana,arial,sans-serif;} -body { font-size: 87.5%;} -h1 { font-size: 1.8em; font-weight: bold;} -h2 { font-size: 1.6em; font-weight: bold;} -h3 { font-size: 1.4em; font-weight: bold;} -h4 { font-size: 1.2em; font-weight: bold;} -h5 { font-size: 1em; font-weight: bold;} -td td, th th, th td, td th { font-size: 100%;} -pre { font-size: 110%;} -form { padding:0; margin:0;} -input { padding-left:1px; padding-right:1px; } +@charset "utf-8"; +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;} ol,ul{list-style:none;} caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} q:before,q:after{content:'';} abbr,acronym {border:0;} +a:link { color: #0000cc; } +body { background: #fff; margin: 3px 8px;} +body, select, input { font-family: verdana,arial,sans-serif;} +body { font-size: 87.5%;} +h1 { font-size: 1.8em; font-weight: bold;} +h2 { font-size: 1.6em; font-weight: bold;} +h3 { font-size: 1.4em; font-weight: bold;} +h4 { font-size: 1.2em; font-weight: bold;} +h5 { font-size: 1em; font-weight: bold;} +td td, th th, th td, td th { font-size: 100%;} +pre { font-size: 110%;} +form { padding:0; margin:0;} +input { padding-left:1px; padding-right:1px; } diff --git a/settings.xml b/settings.xml index ac9a5af..c3f97ad 100644 --- a/settings.xml +++ b/settings.xml @@ -1,29 +1,29 @@ - - - - - Codeforces - - - github - GitHub Packages (Codeforces) - - https://maven.pkg.github.com/Codeforces/codeforces-commons - - - - - - Codeforces - - - - github - ${env.GITHUB_ACTOR} - ${env.GITHUB_TOKEN} - - - + + + + + Codeforces + + + github + GitHub Packages (Codeforces) + + https://maven.pkg.github.com/Codeforces/codeforces-commons + + + + + + Codeforces + + + + github + ${env.GITHUB_ACTOR} + ${env.GITHUB_TOKEN} + + + diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/pom.xml b/tools/nocturne-archetype/src/main/resources/archetype-resources/pom.xml index 2942755..80c0b05 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/pom.xml +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/pom.xml @@ -66,10 +66,10 @@ org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.14.1 - 1.6 - 1.6 + 17 + 17 UTF-8 diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationModule.java b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationModule.java index 9c0edc9..4fc8c9f 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationModule.java +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationModule.java @@ -1,16 +1,16 @@ -package ${packageName}; - -import com.google.inject.Binder; -import com.google.inject.Module; - -/** - * Nocturne uses Google Guice as IoC container. - * This class contains IoC setup. - * See http://code.google.com/p/google-guice/wiki/GettingStarted - */ -public class ApplicationModule implements Module { - @Override - public void configure(Binder binder) { - // No IoC binding. - } -} +package ${packageName}; + +import com.google.inject.Binder; +import com.google.inject.Module; + +/** + * Nocturne uses Google Guice as IoC container. + * This class contains IoC setup. + * See http://code.google.com/p/google-guice/wiki/GettingStarted + */ +public class ApplicationModule implements Module { + @Override + public void configure(Binder binder) { + // No IoC binding. + } +} diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationRequestRouter.java b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationRequestRouter.java index a730417..58190a2 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationRequestRouter.java +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/ApplicationRequestRouter.java @@ -1,20 +1,20 @@ -package ${packageName}; - -import ${packageName}.page.IndexPage; -import org.nocturne.main.LinkedRequestRouter; -import org.nocturne.link.Links; - -/** - * Sample application uses subclass of LinkedRequestRouter as - * a RequestRouter. - *

- * Just add into the links your pages, like: <@code - * // Your page should have @Link annotation. - * Links.add(IndexPage.class); - * } - */ -public class ApplicationRequestRouter extends LinkedRequestRouter { - static { - Links.add(IndexPage.class); - } -} +package ${packageName}; + +import ${packageName}.page.IndexPage; +import org.nocturne.main.LinkedRequestRouter; +import org.nocturne.link.Links; + +/** + * Sample application uses subclass of LinkedRequestRouter as + * a RequestRouter. + *

+ * Just add into the links your pages, like: <@code + * // Your page should have @Link annotation. + * Links.add(IndexPage.class); + * } + */ +public class ApplicationRequestRouter extends LinkedRequestRouter { + static { + Links.add(IndexPage.class); + } +} diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/page/IndexPage.java b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/page/IndexPage.java index 041459b..d8cbb69 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/page/IndexPage.java +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/java/page/IndexPage.java @@ -1,16 +1,16 @@ -package ${packageName}.page; - -import org.nocturne.link.Link; -import org.nocturne.main.Page; - -/** - * Simple controller, which just adds the single - * variable into view layer. - */ -@Link("") -public class IndexPage extends Page { - @Override - public void action() { - put("name", "world"); - } -} +package ${packageName}.page; + +import org.nocturne.link.Link; +import org.nocturne.main.Page; + +/** + * Simple controller, which just adds the single + * variable into view layer. + */ +@Link("") +public class IndexPage extends Page { + @Override + public void action() { + put("name", "world"); + } +} diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/log4j.properties b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/log4j.properties index ea12812..3115544 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/log4j.properties +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/log4j.properties @@ -1,13 +1,13 @@ -log4j.rootCategory=INFO,stdout,log_file -log4j.logger.${groupId}=INFO - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m%n - -log4j.appender.log_file=org.apache.log4j.RollingFileAppender -log4j.appender.log_file.File=${logging.path} -log4j.appender.log_file.MaxFileSize=10MB -log4j.appender.log_file.MaxBackupIndex=10 -log4j.appender.log_file.layout=org.apache.log4j.PatternLayout -log4j.appender.log_file.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m (%F:%L, %t)%n +log4j.rootCategory=INFO,stdout,log_file +log4j.logger.${groupId}=INFO + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m%n + +log4j.appender.log_file=org.apache.log4j.RollingFileAppender +log4j.appender.log_file.File=${logging.path} +log4j.appender.log_file.MaxFileSize=10MB +log4j.appender.log_file.MaxBackupIndex=10 +log4j.appender.log_file.layout=org.apache.log4j.PatternLayout +log4j.appender.log_file.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm:ss,SSS}] %C{1}: %m (%F:%L, %t)%n diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/nocturne.properties b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/nocturne.properties index 60c93b9..0c49bef 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/nocturne.properties +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/resources/nocturne.properties @@ -1,12 +1,12 @@ -nocturne.debug=${nocturne.debug} -nocturne.templates-path=WEB-INF/templates -nocturne.request-router=${packageName}.ApplicationRequestRouter -nocturne.guice-module-class-name=${packageName}.ApplicationModule -nocturne.class-reloading-exceptions= -nocturne.class-reloading-packages=${packageName} -nocturne.reloading-class-paths=${development.home}/target/classes -nocturne.debug-captions-dir=${development.home}/src/main/resources -nocturne.debug-web-resources-dir=${development.home}/src/main/webapp -nocturne.page-request-listeners= -nocturne.reset.strategy=RESET -nocturne.reset.persist-annotations=org.nocturne.reset.annotation.Persist;com.google.inject.Inject +nocturne.debug=${nocturne.debug} +nocturne.templates-path=WEB-INF/templates +nocturne.request-router=${packageName}.ApplicationRequestRouter +nocturne.guice-module-class-name=${packageName}.ApplicationModule +nocturne.class-reloading-exceptions= +nocturne.class-reloading-packages=${packageName} +nocturne.reloading-class-paths=${development.home}/target/classes +nocturne.debug-captions-dir=${development.home}/src/main/resources +nocturne.debug-web-resources-dir=${development.home}/src/main/webapp +nocturne.page-request-listeners= +nocturne.reset.strategy=RESET +nocturne.reset.persist-annotations=org.nocturne.reset.annotation.Persist;com.google.inject.Inject diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/templates/IndexPage.ftl b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/templates/IndexPage.ftl index f4818ff..e411033 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/templates/IndexPage.ftl +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/templates/IndexPage.ftl @@ -1,14 +1,14 @@ -<#setting url_escaping_charset='UTF-8'> - - - - - - - Nocturne hello world - - - - {{Hello}}, ${name} - - +<#setting url_escaping_charset='UTF-8'> + + + + + + + Nocturne hello world + + + + {{Hello}}, ${name} + + diff --git a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/css/reset.css b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/css/reset.css index 24865a1..6ff4113 100644 --- a/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/css/reset.css +++ b/tools/nocturne-archetype/src/main/resources/archetype-resources/src/main/webapp/css/reset.css @@ -1,15 +1,15 @@ -@charset "utf-8"; -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;} ol,ul{list-style:none;} caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} q:before,q:after{content:'';} abbr,acronym {border:0;} -a:link { color: #0000cc; } -body { background: #fff; margin: 3px 8px;} -body, select, input { font-family: verdana,arial,sans-serif;} -body { font-size: 87.5%;} -h1 { font-size: 1.8em; font-weight: bold;} -h2 { font-size: 1.6em; font-weight: bold;} -h3 { font-size: 1.4em; font-weight: bold;} -h4 { font-size: 1.2em; font-weight: bold;} -h5 { font-size: 1em; font-weight: bold;} -td td, th th, th td, td th { font-size: 100%;} -pre { font-size: 110%;} -form { padding:0; margin:0;} -input { padding-left:1px; padding-right:1px; } +@charset "utf-8"; +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;} ol,ul{list-style:none;} caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} q:before,q:after{content:'';} abbr,acronym {border:0;} +a:link { color: #0000cc; } +body { background: #fff; margin: 3px 8px;} +body, select, input { font-family: verdana,arial,sans-serif;} +body { font-size: 87.5%;} +h1 { font-size: 1.8em; font-weight: bold;} +h2 { font-size: 1.6em; font-weight: bold;} +h3 { font-size: 1.4em; font-weight: bold;} +h4 { font-size: 1.2em; font-weight: bold;} +h5 { font-size: 1em; font-weight: bold;} +td td, th th, th td, td th { font-size: 100%;} +pre { font-size: 110%;} +form { padding:0; margin:0;} +input { padding-left:1px; padding-right:1px; } diff --git a/tools/read.me b/tools/read.me index eff563d..a5f6251 100644 --- a/tools/read.me +++ b/tools/read.me @@ -1,8 +1,8 @@ -Run: - "mvn install" in nocturne-archetype (or install.bat) - -And after run: - - mvn archetype:create -DarchetypeGroupId=org.nocturne.archetypes \ - -DarchetypeArtifactId=nocturne-archetype -DarchetypeVersion=1.0.1-SNAPSHOT \ - -DgroupId=? -DartifactId=? +Run: + "mvn install" in nocturne-archetype (or install.bat) + +And after run: + + mvn archetype:create -DarchetypeGroupId=org.nocturne.archetypes \ + -DarchetypeArtifactId=nocturne-archetype -DarchetypeVersion=1.0.1-SNAPSHOT \ + -DgroupId=? -DartifactId=?