Popular Posts
javax.net.ssl.SSLHandshakeException: Connection closed by peer in Android 5.0 Lollipop Recently, there is a error occurs when access website via ssl connection like below although it worked fine several days ago. // Enable SSL... Enable SSL connection for Jsoup import org.jsoup.Connection; import org.jsoup.Jsoup; import javax.net.ssl.*; import java.io.IOException; import java.security.KeyManagement... Build an OpenVPN server on android device Preparation An android device, in this case, Sony xperia Z is used Root permission required Linux Deploy for deploy i...
Blog Archive
Stats
Compile source via JavaCompiler
  1. import javax.tools.*;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.PrintWriter;
  5. import java.io.StringWriter;
  6. import java.lang.reflect.InvocationTargetException;
  7. import java.net.URI;
  8. import java.net.URL;
  9. import java.net.URLClassLoader;
  10. import java.util.Arrays;
  11.  
  12. /**
  13.  * Created by nanashi07 on 15/12/13.
  14.  */
  15. public class CompileOnFly {
  16.  
  17.     public static void main(String args[]) throws IOException {
  18.         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  19.         DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
  20.  
  21.         StringWriter writer = new StringWriter();
  22.         PrintWriter out = new PrintWriter(writer);
  23.         out.println("package com.prhythm.output;");
  24.         out.println();
  25.         out.println("public class OutputImpl implements IOutput {");
  26.         out.println();
  27.         out.println("   public void printFor(String message) {");
  28.         out.println("       System.out.printf(\"Print from %s : %s%n\", getClass().getName(), message);");
  29.         out.println("   }");
  30.         out.println();
  31.         out.println("}");
  32.         out.close();
  33.         JavaFileObject file = new TextJavaObject("com.prhythm.output.OutputImpl", writer.toString());
  34.  
  35.         Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
  36.         JavaCompiler.CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
  37.  
  38.         boolean success = task.call();
  39.         for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
  40.             System.out.printf("Code: %s%n", diagnostic.getCode());
  41.             System.out.printf("Kind: %s%n", diagnostic.getKind());
  42.             System.out.printf("Position: %s%n", diagnostic.getPosition());
  43.             System.out.printf("StartPosition: %s%n", diagnostic.getStartPosition());
  44.             System.out.printf("EndPosition: %s%n", diagnostic.getEndPosition());
  45.             System.out.printf("Source: %s%n", diagnostic.getSource());
  46.             System.out.printf("Message: %s%n", diagnostic.getMessage(null));
  47.         }
  48.  
  49.         if (success) {
  50.             try {
  51.                 URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{new File("").toURI().toURL()});
  52.                 Class<?> outClass = Class.forName("com.prhythm.output.OutputImpl", true, classLoader);
  53.                 Object outInstance = outClass.newInstance();
  54.                 outClass.getDeclaredMethod("printFor", new Class[]{String.class}).invoke(outInstance, "Bruce");
  55.             } catch (ClassNotFoundException e) {
  56.                 System.err.println("Class not found: " + e);
  57.             } catch (NoSuchMethodException e) {
  58.                 System.err.println("No such method: " + e);
  59.             } catch (IllegalAccessException e) {
  60.                 System.err.println("Illegal access: " + e);
  61.             } catch (InvocationTargetException e) {
  62.                 System.err.println("Invocation target: " + e);
  63.             } catch (InstantiationException e) {
  64.                 System.err.println("Initialize instance: " + e);
  65.             }
  66.         }
  67.     }
  68. }
  69.  
  70. class TextJavaObject extends SimpleJavaFileObject {
  71.     final String code;
  72.  
  73.     TextJavaObject(String name, String code) {
  74.         super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
  75.         this.code = code;
  76.     }
  77.  
  78.     @Override
  79.     public CharSequence getCharContent(boolean ignoreEncodingErrors) {
  80.         return code;
  81.     }
  82. }

Output:

  1. Print from com.prhythm.output.OutputImpl : Bruce
Annotation processor on Intellij IDEA
Project structure
+ ---- annotation- processor
| + ---- annotation- lib
|   + ---- build.gradle
|   + ---- src
|     + ---- main
|     | + ---- java
|     |   + ---- com
|     |     + ---- prhythm
|     |       + ---- annotations
|     |         + ---- PrintUsage.java
|     |         + ---- PrintUsageProcessor.java
|     | + ---- resources
|         + ---- META-INF
|           + ---- services
|             + ---- javax.annotation.processing.Processor
| + ---- foo
|   + ---- build.gradle
|   + ---- src
|     + ---- main
|       + ---- java
|         + ---- com
|           + ---- prhythm
|             + ---- foo
|               + ---- Bar.java
| + ---- build.gradle
| + ---- settings.gradle

PrintUsage.java
  1. package com.prhythm.annotations;
  2.  
  3. /**
  4. * Print usage
  5. * Created by nanashi07 on 15/12/2.
  6. */
  7. public @interface PrintUsage {
  8. }

PrintUsageProcessor.java
  1. package com.prhythm.annotations;
  2.  
  3. import javax.annotation.processing.AbstractProcessor;
  4. import javax.annotation.processing.Messager;
  5. import javax.annotation.processing.RoundEnvironment;
  6. import javax.annotation.processing.SupportedAnnotationTypes;
  7. import javax.lang.model.element.Element;
  8. import javax.lang.model.element.TypeElement;
  9. import javax.tools.Diagnostic;
  10. import java.util.Set;
  11.  
  12. /**
  13. * Processor of {@link PrintUsage}
  14. * Created by nanashi07 on 15/12/2.
  15. */
  16. @SupportedAnnotationTypes("com.prhythm.annotations.PrintUsage")
  17. public class PrintUsageProcessor extends AbstractProcessor {
  18. @Override
  19. public boolean process(Set annotations, RoundEnvironment roundEnv) {
  20. Messager messager = processingEnv.getMessager();
  21. for (TypeElement typeElement : annotations) {
  22. for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
  23. String msg = String.format("Use %s on %s#%s", typeElement, element.getEnclosingElement(), element);
  24. messager.printMessage(Diagnostic.Kind.NOTE, msg);
  25. }
  26. }
  27. return true;
  28. }
  29. }

javax.annotation.processing.Processor
  1. com.prhythm.annotations.PrintUsageProcessor

Bar.java
  1. package com.prhythm.foo;
  2.  
  3. import com.prhythm.annotations.PrintUsage;
  4.  
  5. /**
  6. * Test class
  7. * Created by nanashi07 on 15/12/2.
  8. */
  9. public class Bar {
  10.  
  11. @PrintUsage
  12. public void move(String name) {
  13.  
  14. }
  15.  
  16. @PrintUsage
  17. public long time() {
  18. return System.currentTimeMillis();
  19. }
  20.  
  21. }

build.gradle (foo)
  1. group 'com.prhythm'
  2. version '1.0-SNAPSHOT'
  3.  
  4. apply plugin: 'java'
  5.  
  6. sourceCompatibility = 1.6
  7.  
  8. repositories {
  9. mavenCentral()
  10. }
  11.  
  12. dependencies {
  13. testCompile group: 'junit', name: 'junit', version: '4.11'
  14. compile project(':annotation-lib')
  15. }

Enable annotation processor on preference




Build with gradle

  1. gradle clean build

result


References:



Type Memo

Note: ==================================================
Note: TypeElement: com.prhythm.annotations.PrintUsage
Note: TypeElement.getClass(): class com.sun.tools.javac.code.Symbol$ClassSymbol
Note: TypeElement.getSimpleName(): PrintUsage
Note: TypeElement.getEnclosedElements(): 
Note: TypeElement.getEnclosingElement(): com.prhythm.annotations
Note: TypeElement.getInterfaces(): java.lang.annotation.Annotation
Note: TypeElement.getNestingKind(): TOP_LEVEL
Note: TypeElement.getQualifiedName(): com.prhythm.annotations.PrintUsage
Note: TypeElement.getSuperclass(): none
Note: TypeElement.getTypeParameters(): 
Note: TypeElement.getAnnotationMirrors(): 
Note: TypeElement.asType(): com.prhythm.annotations.PrintUsage
Note: TypeElement.getKind(): ANNOTATION_TYPE
Note: TypeElement.getModifiers(): [public, abstract]
Note: ----------------------------------------------------------
Note: Element: move(java.lang.String)
Note: Element.getModifiers(): [public]
Note: Element.getClass(): class com.sun.tools.javac.code.Symbol$MethodSymbol
Note: Element.getKind(): METHOD
Note: Element.getAnnotationMirrors(): @com.prhythm.annotations.PrintUsage
Note: Element.getSimpleName(): move
Note: Element.asType(): (java.lang.String)void
Note: Element.getEnclosedElements(): 
Note: Element.getEnclosingElement(): com.prhythm.foo.Bar
Note: ----------------------------------------------------------
Note: Element: time()
Note: Element.getModifiers(): [public]
Note: Element.getClass(): class com.sun.tools.javac.code.Symbol$MethodSymbol
Note: Element.getKind(): METHOD
Note: Element.getAnnotationMirrors(): @com.prhythm.annotations.PrintUsage
Note: Element.getSimpleName(): time
Note: Element.asType(): ()long
Note: Element.getEnclosedElements(): 
Note: Element.getEnclosingElement(): com.prhythm.foo.Bar
Note: ==================================================
Note: TypeElement: com.prhythm.annotations.Mark
Note: TypeElement.getClass(): class com.sun.tools.javac.code.Symbol$ClassSymbol
Note: TypeElement.getSimpleName(): Mark
Note: TypeElement.getEnclosedElements(): value(),names()
Note: TypeElement.getEnclosingElement(): com.prhythm.annotations
Note: TypeElement.getInterfaces(): java.lang.annotation.Annotation
Note: TypeElement.getNestingKind(): TOP_LEVEL
Note: TypeElement.getQualifiedName(): com.prhythm.annotations.Mark
Note: TypeElement.getSuperclass(): none
Note: TypeElement.getTypeParameters(): 
Note: TypeElement.getAnnotationMirrors(): 
Note: TypeElement.asType(): com.prhythm.annotations.Mark
Note: TypeElement.getKind(): ANNOTATION_TYPE
Note: TypeElement.getModifiers(): [public, abstract]
Note: ----------------------------------------------------------
Note: Element: com.prhythm.foo.Yahoo
Note: Element.getModifiers(): [public, abstract]
Note: Element.getClass(): class com.sun.tools.javac.code.Symbol$ClassSymbol
Note: Element.getKind(): INTERFACE
Note: Element.getAnnotationMirrors(): @com.prhythm.annotations.Mark("2323")
Note: Element.getSimpleName(): Yahoo
Note: Element.asType(): com.prhythm.foo.Yahoo
Note: Element.getEnclosedElements(): home(java.lang.String)
Note: Element.getEnclosingElement(): com.prhythm.foo
Note: ----------------------------------------------------------
Note: Element: home(java.lang.String)
Note: Element.getModifiers(): [public, abstract]
Note: Element.getClass(): class com.sun.tools.javac.code.Symbol$MethodSymbol
Note: Element.getKind(): METHOD
Note: Element.getAnnotationMirrors(): @com.prhythm.annotations.Mark("110"),@com.prhythm.core.generic.http.annotation.Get("https://tw.yahoo.com")
Note: Element.getSimpleName(): home
Note: Element.asType(): (java.lang.String)java.lang.String
Note: Element.getEnclosedElements(): 
Note: Element.getEnclosingElement(): com.prhythm.foo.Yahoo
MEMO: Url rewrite on default site of IIS

Application Request Rounting is required. Install using web platform installer.

Enable Application Request Rounting.

Set server proxy setting.

Set Enable proxy checked and Enable disk cache unchecked.

Binding host name on default site. Using www.mvc.com as example.

Rewrite www.mvc.com to http://localhost:8080/mvc

Add rewrite rule on default site.

Add rule using blank rule template.

Set rewrite rule for www.mvc.com

The result of www.mvc.com

Add rewrite rule for other iis site on localhost. First, create a new web site, and add an host reference www.mysite.me.

Set rewrite rule for www.mysite.com

Add host binding of default site

The result of www.mysite.com

Display soap message in axis client

client-config.wsdd : should be placed in the root of classpath

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <deployment name="defaultClientConfig" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  3.  
  4.     <handler name="log" type="java:org.apache.axis.handlers.LogHandler">
  5.         <!-- If true, output SOAP messages in the console ; otherwise, output in a file. -->
  6.         <parameter name="LogHandler.writeToConsole" value="false" />
  7.         <!-- Specifies the name of the output file when LogHandler.writeToConsole is false -->
  8.         <parameter name="LogHandler.fileName" value="axis.log" />
  9.     </handler>
  10.  
  11.     <globalConfiguration>
  12.         <parameter name="disablePrettyXML" value="false" />
  13.         <requestFlow>
  14.             <handler type="log" />
  15.         </requestFlow>
  16.         <responseFlow>
  17.             <handler type="log" />
  18.         </responseFlow>
  19.     </globalConfiguration>
  20.  
  21.     <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender" />
  22.     <transport name="local" pivot="java:org.apache.axis.transport.local.LocalSender" />
  23.     <transport name="java" pivot="java:org.apache.axis.transport.java.JavaSender" />
  24. </deployment>
JUnit with Spring : orderly test method

When using junit with spring framework for test, to sort execute order of method, will use FixMethodOrder class. It is a little inconvenience because you need to name your test method in correct way.



Execute test method ordered by method name
  1. package com.prhythm.test;
  2.  
  3. import org.junit.FixMethodOrder;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.junit.runners.MethodSorters;
  7. import org.springframework.test.context.ContextConfiguration;
  8. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  9.  
  10. /**
  11.  * Created by nanashi07 on 15/6/20.
  12.  */
  13. @RunWith(SpringJUnit4ClassRunner.class)
  14. @ContextConfiguration("classpath:spring/application-context.xml")
  15. @FixMethodOrder(MethodSorters.NAME_ASCENDING)
  16. public class OrderedTest {
  17.  
  18.     @Test
  19.     public void test1() {
  20.         System.out.println("test1 executed");
  21.     }
  22.  
  23.     @Test
  24.     public void test2() {
  25.         System.out.println("test2 executed");
  26.     }
  27.  
  28.     @Test
  29.     public void test3() {
  30.         System.out.println("test3 executed");
  31.     }
  32.  
  33.     @Test
  34.     public void test4() {
  35.         System.out.println("test4 executed");
  36.     }
  37. }
Result:
  1. test1 executed
  2. test2 executed
  3. test3 executed
  4. test4 executed


Now there is an another way to do it.



Create a order annotation for customize order
  1. package org.springframework.test.context.junit4;
  2.  
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5.  
  6. /**
  7.  * Created by nanashi07 on 15/6/20.
  8.  */
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface Order {
  11.     int value();
  12. }
Create a junit runner extends SpringJUnit4ClassRunner
  1. package org.springframework.test.context.junit4;
  2.  
  3. import org.junit.runners.model.FrameworkMethod;
  4. import org.junit.runners.model.InitializationError;
  5. import org.springframework.test.context.TestContextManager;
  6.  
  7. import java.util.Collections;
  8. import java.util.Comparator;
  9. import java.util.List;
  10.  
  11. public class SpringJUnit4ClassOrderedRunner extends SpringJUnit4ClassRunner {
  12.  
  13.     /**
  14.      * Constructs a new {@code SpringJUnit4ClassRunner} and initializes a
  15.      * {@link TestContextManager} to provide Spring testing functionality to
  16.      * standard JUnit tests.
  17.      *
  18.      * @param clazz the test class to be run
  19.      * @see #createTestContextManager(Class)
  20.      */
  21.     public SpringJUnit4ClassOrderedRunner(Class<?> clazz) throws InitializationError {
  22.         super(clazz);
  23.     }
  24.  
  25.     @Override
  26.     protected List<FrameworkMethod> computeTestMethods() {
  27.         List<FrameworkMethod> list = super.computeTestMethods();
  28.         Collections.sort(list, new Comparator<FrameworkMethod>() {
  29.             @Override
  30.             public int compare(FrameworkMethod f1, FrameworkMethod f2) {
  31.                 Order o1 = f1.getAnnotation(Order.class);
  32.                 Order o2 = f2.getAnnotation(Order.class);
  33.  
  34.                 if (o1 == null || o2 == null)
  35.                     return -1;
  36.  
  37.                 return o1.value() - o2.value();
  38.             }
  39.         });
  40.         return list;
  41.     }
  42. }
Run test as customized order
  1. package com.prhythm.test;
  2.  
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.test.context.ContextConfiguration;
  6. import org.springframework.test.context.junit4.Order;
  7. import org.springframework.test.context.junit4.SpringJUnit4ClassOrderedRunner;
  8.  
  9. /**
  10.  * Created by nanashi07 on 15/6/20.
  11.  */
  12. @RunWith(SpringJUnit4ClassOrderedRunner.class)
  13. @ContextConfiguration("classpath:spring/application-context.xml")
  14. public class OrderedTest {
  15.  
  16.     @Test
  17.     @Order(value = 4)
  18.     public void test1() {
  19.         System.out.println("test1 executed");
  20.     }
  21.  
  22.     @Test
  23.     @Order(value = 3)
  24.     public void test2() {
  25.         System.out.println("test2 executed");
  26.     }
  27.  
  28.     @Test
  29.     @Order(value = 2)
  30.     public void test3() {
  31.         System.out.println("test3 executed");
  32.     }
  33.  
  34.     @Test
  35.     @Order(value = 1)
  36.     public void test4() {
  37.         System.out.println("test4 executed");
  38.     }
  39. }
Result:
  1. test4 executed
  2. test3 executed
  3. test2 executed
  4. test1 executed
Tired of Hibernate? Try JDBI in your code

JDBI Quick sample



ICategoryDAO.java : create a data access interface (implement is not required)
  1. package com.prhythm.erotic.task.data.dao;
  2.  
  3. import com.prhythm.erotic.entity.source.Category;
  4. import com.prhythm.erotic.task.data.mapper.CategoryMapper;
  5. import org.skife.jdbi.v2.sqlobject.Bind;
  6. import org.skife.jdbi.v2.sqlobject.BindBean;
  7. import org.skife.jdbi.v2.sqlobject.SqlQuery;
  8. import org.skife.jdbi.v2.sqlobject.SqlUpdate;
  9. import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
  10. import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
  11.  
  12. import java.util.List;
  13.  
  14. /**
  15.  * Created by nanashi07 on 15/6/14.
  16.  */
  17. @RegisterMapper(CategoryMapper.class)
  18. public interface ICategoryDAO extends Transactional<ICategoryDAO> {
  19.  
  20.     @SqlUpdate("insert into Category (Source, Url, Category, SubCategory, Enabled) values (:source, :url, :category, :subCategory, :enabled)")
  21.     int insert(@BindBean Category category);
  22.  
  23.     @SqlUpdate("update Category set Source = :source, Category = :category, SubCategory = :subCategory, Enabled = :enabled where Url = :url")
  24.     int update(@BindBean Category category);
  25.  
  26.     @SqlUpdate("update Category set Enabled = :enabled")
  27.     int setEnabled(@Bind("enabled") boolean enabled);
  28.  
  29.     @SqlQuery("select * from Category where Url = :url")
  30.     Category findCategory(@Bind("url") String url);
  31.  
  32.     @SqlQuery("select * from Category")
  33.     List<Category> getCategories();
  34.  
  35.     void close();
  36. }

CategoryMapper.java : create a object mapper for convertion
  1. package com.prhythm.erotic.task.data.mapper;
  2.  
  3. import com.prhythm.erotic.entity.source.Category;
  4. import org.skife.jdbi.v2.StatementContext;
  5. import org.skife.jdbi.v2.tweak.ResultSetMapper;
  6.  
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9.  
  10. /**
  11.  * Created by nanashi07 on 15/6/14.
  12.  */
  13. public class CategoryMapper extends MetaDataSupport implements ResultSetMapper<Category> {
  14.  
  15.     @Override
  16.     public Category map(int index, ResultSet r, StatementContext ctx) throws SQLException {
  17.         Category category = new Category();
  18.         if (hasColumn(r, "source")) category.setSource(r.getString("source"));
  19.         if (hasColumn(r, "url")) category.setUrl(r.getString("url"));
  20.         if (hasColumn(r, "category")) category.setCategory(r.getString("category"));
  21.         if (hasColumn(r, "subCategory")) category.setSubCategory(r.getString("subCategory"));
  22.         if (hasColumn(r, "enabled")) category.setEnabled(r.getBoolean("enabled"));
  23.         return category;
  24.     }
  25.  
  26. }

ICategoryService.java : create a data access service interface
  1. package com.prhythm.erotic.task.data;
  2.  
  3. import com.prhythm.erotic.entity.source.Category;
  4.  
  5. import java.util.Collection;
  6. import java.util.List;
  7.  
  8. /**
  9.  * Created by nanashi07 on 15/6/14.
  10.  */
  11. public interface ICategoryService {
  12.  
  13.     /**
  14.      * Retrive all categories
  15.      *
  16.      * @return
  17.      */
  18.     List<Category> getCategories();
  19.  
  20.     /**
  21.      * Update categories
  22.      *
  23.      * @param categories
  24.      * @return
  25.      */
  26.     int updateCategories(Collection<Category> categories);
  27. }

CategoryServiceImpl.java : Implement the data access interface
  1. package com.prhythm.erotic.task.data.impl;
  2.  
  3. import com.prhythm.erotic.entity.source.Category;
  4. import com.prhythm.erotic.logging.LogHandler;
  5. import com.prhythm.erotic.task.data.ICategoryService;
  6. import com.prhythm.erotic.task.data.dao.ICategoryDAO;
  7. import org.skife.jdbi.v2.Handle;
  8. import org.skife.jdbi.v2.IDBI;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.context.ApplicationContext;
  11. import org.springframework.stereotype.Service;
  12.  
  13. import java.util.Collection;
  14. import java.util.List;
  15.  
  16. /**
  17.  * Created by nanashi07 on 15/6/14.
  18.  */
  19. @Service
  20. public class CategoryServiceImpl implements ICategoryService {
  21.  
  22.     @Autowired
  23.     IDBI dbi;
  24.  
  25.     @Override
  26.     public List<Category> getCategories() {
  27.         Handle h = dbi.open();
  28.         ICategoryDAO dao = h.attach(ICategoryDAO.class);
  29.  
  30.         List<Category> categories = dao.getCategories();
  31.  
  32.         dao.close();
  33.         h.close();
  34.  
  35.         return categories;
  36.     }
  37.  
  38.     @Override
  39.     public int updateCategories(Collection<Category> categories) {
  40.         int count = 0;
  41.  
  42.         Handle handle = dbi.open();
  43.  
  44.         try {
  45.             updateCategories(handle, categories);
  46.             handle.commit();
  47.         } catch (Exception e) {
  48.             LogHandler.error(e);
  49.             handle.rollback();
  50.         } finally {
  51.             handle.close();
  52.         }
  53.  
  54.         return count;
  55.     }
  56.  
  57.     int updateCategories(Handle handle, Collection<Category> categories) {
  58.         int count = 0;
  59.  
  60.         ICategoryDAO dao = handle.attach(ICategoryDAO.class);
  61.  
  62.         // Disable all items
  63.         dao.setEnabled(false);
  64.  
  65.         for (Category c : categories) {
  66.             Category found = dao.findCategory(c.getUrl());
  67.             if (found == null) {
  68.                 count += dao.insert(c);
  69.             } else {
  70.                 count += dao.update(c);
  71.             }
  72.         }
  73.  
  74.         return count;
  75.     }
  76. }

CategoryServiceTester.java : test usage
  1. package com.prhythm.erotic.test.dao;
  2.  
  3. import com.google.common.base.Stopwatch;
  4. import com.prhythm.erotic.entity.source.Category;
  5. import com.prhythm.erotic.logging.LogHandler;
  6. import com.prhythm.erotic.task.data.ICategoryService;
  7. import org.junit.Before;
  8. import org.junit.Test;
  9. import org.junit.runner.RunWith;
  10. import org.skife.jdbi.v2.IDBI;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.context.ApplicationContext;
  13. import org.springframework.test.context.ContextConfiguration;
  14. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  15.  
  16. import java.util.Arrays;
  17. import java.util.List;
  18.  
  19. /**
  20.  * Created by nanashi07 on 15/6/14.
  21.  */
  22. @RunWith(SpringJUnit4ClassRunner.class)
  23. @ContextConfiguration("classpath:spring/application-context.xml")
  24. public class CategoryServiceTester {
  25.  
  26.     @Autowired
  27.     ICategoryService categoryService;
  28.  
  29.     @Test
  30.     public void testGetCategories() {
  31.         Stopwatch stopwatch = Stopwatch.createStarted();
  32.         List<Category> categories = categoryService.getCategories();
  33.         stopwatch.stop();
  34.         LogHandler.info("%d items loaded in %s", categories.size(), stopwatch);
  35.     }
  36.  
  37.     @Test
  38.     public void testUpdateCategories() {
  39.         // Get current items
  40.         List<Category> categories = categoryService.getCategories();
  41.         // Append 10 new items
  42.         for (int i = 0; i < 10; i++) {
  43.             Category c = new Category();
  44.             c.setSource("Prhythm");
  45.             c.setUrl("http://app.prhtyhm.com/sample/" + UUID.randomUUID());
  46.             c.setCategory("Blog");
  47.             c.setEnabled(true);
  48.             categories.add(c);
  49.         }
  50.  
  51.         Stopwatch stopwatch = Stopwatch.createStarted();
  52.         int count = categoryService.updateCategories(categories);
  53.         stopwatch.stop();
  54.         LogHandler.info("%d items updated in %s", count, stopwatch);
  55.     }
  56.  
  57. }

datasourc.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:tx="http://www.springframework.org/schema/tx"
  5.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  6.                            http://www.springframework.org/schema/beans/spring-beans.xsd
  7.                            http://www.springframework.org/schema/tx
  8.                            http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
  9.  
  10.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  11.         <property name="driverClassName" value="${com.prhythm.erotic.datasource.driver}"/>
  12.         <property name="url" value="${com.prhythm.erotic.datasource.temp.url}"/>
  13.         <property name="username" value="${com.prhythm.erotic.datasource.user}"/>
  14.         <property name="password" value="${com.prhythm.erotic.datasource.password}"/>
  15.     </bean>
  16.  
  17.     <tx:annotation-driven transaction-manager="txManager"/>
  18.  
  19.     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  20.         <constructor-arg ref="dataSource"/>
  21.     </bean>
  22.  
  23.     <bean id="txDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
  24.         <constructor-arg ref="dataSource"/>
  25.     </bean>
  26.  
  27.     <bean class="org.skife.jdbi.v2.DBI" depends-on="txManager">
  28.         <constructor-arg ref="txDataSource"/>
  29.         <property name="SQLLog">
  30.             <bean class="com.prhythm.erotic.task.logging.SQLLogAppender"/>
  31.         </property>
  32.     </bean>
  33.  
  34. </beans>
javax.net.ssl.SSLHandshakeException: Connection closed by peer in Android 5.0 Lollipop

Recently, there is a error occurs when access website via ssl connection like below although it worked fine several days ago.

  1. // Enable SSL connection
  2. HttpsURLConnection.setDefaultSSLSocketFactory(new MySocketFactory1());
  3. HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
  4. public boolean verify(String hostname, SSLSession session) {
  5. return true;
  6. }
  7. });
  8.  
  9. URL address = new URL(url);
  10. HttpsURLConnection urlConnection = (HttpsURLConnection) address.openConnection();
  11. InputStream in = urlConnection.getInputStream();
  12. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  13. byte[] buffer = new byte[128];
  14. int len = 0;
  15. while ((len = in.read(buffer)) > 0) {
  16. baos.write(buffer, 0, len);
  17. }
  18. baos.close();
  19. in.close();
MySocketFactory1.java
  1. package com.prhythm.google.myapplication2.app;
  2.  
  3. import javax.net.ssl.SSLContext;
  4. import javax.net.ssl.SSLSocketFactory;
  5. import javax.net.ssl.X509TrustManager;
  6. import java.io.IOException;
  7. import java.net.InetAddress;
  8. import java.net.Socket;
  9. import java.net.UnknownHostException;
  10. import java.security.KeyManagementException;
  11. import java.security.NoSuchAlgorithmException;
  12. import java.security.SecureRandom;
  13. import java.security.cert.CertificateException;
  14. import java.security.cert.X509Certificate;
  15.  
  16. /**
  17. * Created by nanashi07 on 15/5/23.
  18. */
  19. public class MySocketFactory1 extends SSLSocketFactory {
  20.  
  21. SSLContext context;
  22. SSLSocketFactory factory;
  23.  
  24. public MySocketFactory1() {
  25. try {
  26. init();
  27. } catch (KeyManagementException e) {
  28. e.printStackTrace();
  29. } catch (NoSuchAlgorithmException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33.  
  34. void init() throws KeyManagementException, NoSuchAlgorithmException {
  35. context = SSLContext.getInstance("SSL");
  36. context.init(null, new X509TrustManager[]{new X509TrustManager() {
  37. public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  38. }
  39.  
  40. public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  41. }
  42.  
  43. public X509Certificate[] getAcceptedIssuers() {
  44. return new X509Certificate[0];
  45. }
  46. }}, new SecureRandom());
  47. factory = context.getSocketFactory();
  48. }
  49.  
  50. @Override
  51. public String[] getDefaultCipherSuites() {
  52. return factory.getDefaultCipherSuites();
  53. }
  54.  
  55. @Override
  56. public String[] getSupportedCipherSuites() {
  57. return factory.getSupportedCipherSuites();
  58. }
  59.  
  60. @Override
  61. public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
  62. return factory.createSocket(s, host, port, autoClose);
  63. }
  64.  
  65. @Override
  66. public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
  67. return factory.createSocket(host, port);
  68. }
  69.  
  70. @Override
  71. public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
  72. return factory.createSocket(host, port, localHost, localPort);
  73. }
  74.  
  75. @Override
  76. public Socket createSocket(InetAddress host, int port) throws IOException {
  77. return factory.createSocket(host, port);
  78. }
  79.  
  80. @Override
  81. public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
  82. return factory.createSocket(address, port, localAddress, localPort);
  83. }
  84. }
javax.net.ssl.SSLHandshakeException: Connection closed by peer
  1. 05-24 07:54:11.626 1697-1716/com.prhythm.google.myapplication2.app E/Test error
  2. javax.net.ssl.SSLHandshakeException: Connection closed by peer
  3. at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
  4. at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:302)
  5. at com.android.okhttp.Connection.upgradeToTls(Connection.java:197)
  6. at com.android.okhttp.Connection.connect(Connection.java:151)
  7. at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:276)
  8. at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:211)
  9. at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:373)
  10. at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:323)
  11. at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:190)
  12. at com.android.okhttp.internal.http.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:210)
  13. at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:25)
  14. at com.prhythm.google.myapplication2.app.MainActivity$1.doInBackground(MainActivity.java:41)
  15. at com.prhythm.google.myapplication2.app.MainActivity$1.doInBackground(MainActivity.java:26)
  16. at android.os.AsyncTask$2.call(AsyncTask.java:288)
  17. at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  18. at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
  19. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
  20. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
  21. at java.lang.Thread.run(Thread.java:818)

After track and search from some technical articles, I found this TLS/SSL Default Configuration Changes, finally. Base on this, I've made some changes for my SSLSocketFactory to add cipher suites setting. Now it works fine again.

MySocketFactory2.java
  1. package com.prhythm.google.myapplication2.app;
  2.  
  3. import com.prhythm.core.generic.util.Cube;
  4.  
  5. import javax.net.ssl.*;
  6. import java.io.IOException;
  7. import java.net.InetAddress;
  8. import java.net.Socket;
  9. import java.net.UnknownHostException;
  10. import java.security.KeyManagementException;
  11. import java.security.NoSuchAlgorithmException;
  12. import java.security.SecureRandom;
  13. import java.security.cert.CertificateException;
  14. import java.security.cert.X509Certificate;
  15. import java.util.Arrays;
  16. import java.util.Set;
  17.  
  18. /**
  19.  * Created by nanashi07 on 15/5/23.
  20.  */
  21. public class MySocketFactory2 extends SSLSocketFactory {
  22.  
  23.     SSLContext context;
  24.     SSLSocketFactory factory;
  25.     String type;
  26.  
  27.     public MySocketFactory2() {
  28.         try {
  29.             init();
  30.         } catch (KeyManagementException e) {
  31.             e.printStackTrace();
  32.         } catch (NoSuchAlgorithmException e) {
  33.             e.printStackTrace();
  34.         }
  35.     }
  36.  
  37.     void init() throws KeyManagementException, NoSuchAlgorithmException {
  38.         context = SSLContext.getInstance(type = Cube.from("SSL", "TLS").random());
  39.         context.init(null, new TrustManager[]{new X509TrustManager() {
  40.             public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  41.                 System.out.printf("[CLIENT] chain = %s, authType = %s%n", Arrays.toString(chain), authType);
  42.             }
  43.  
  44.             public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  45.                 System.out.printf("[SERVER] chain = %s, authType = %s%n", Arrays.toString(chain), authType);
  46.             }
  47.  
  48.             public X509Certificate[] getAcceptedIssuers() {
  49.                 return new X509Certificate[0];
  50.             }
  51.         }}, new SecureRandom());
  52.         factory = context.getSocketFactory();
  53.     }
  54.  
  55.     @Override
  56.     public String[] getDefaultCipherSuites() {
  57.         return getCipherSuites().toArray(String.class);
  58.     }
  59.  
  60.     @Override
  61.     public String[] getSupportedCipherSuites() {
  62.         return getCipherSuites().toArray(String.class);
  63.     }
  64.  
  65.     @Override
  66.     public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
  67.         return configCipherSuites(factory.createSocket(s, host, port, autoClose));
  68.     }
  69.  
  70.     @Override
  71.     public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
  72.         return configCipherSuites(factory.createSocket(host, port));
  73.     }
  74.  
  75.     @Override
  76.     public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
  77.         return configCipherSuites(factory.createSocket(host, port, localHost, localPort));
  78.     }
  79.  
  80.     @Override
  81.     public Socket createSocket(InetAddress host, int port) throws IOException {
  82.         return configCipherSuites(factory.createSocket(host, port));
  83.     }
  84.  
  85.     @Override
  86.     public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
  87.         return configCipherSuites(factory.createSocket(address, port, localAddress, localPort));
  88.     }
  89.  
  90.     Cube<String> getCipherSuites() {
  91.         return Cube.from("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
  92.                 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
  93.                 "SSL_DHE_DSS_WITH_DES_CBC_SHA",
  94.                 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
  95.                 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
  96.                 "SSL_DHE_RSA_WITH_DES_CBC_SHA",
  97.                 "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
  98.                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
  99.                 "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
  100.                 "SSL_DH_anon_WITH_DES_CBC_SHA",
  101.                 "SSL_DH_anon_WITH_RC4_128_MD5",
  102.                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
  103.                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
  104.                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
  105.                 "SSL_RSA_WITH_DES_CBC_SHA",
  106.                 "SSL_RSA_WITH_NULL_MD5",
  107.                 "SSL_RSA_WITH_NULL_SHA",
  108.                 "SSL_RSA_WITH_RC4_128_MD5",
  109.                 "SSL_RSA_WITH_RC4_128_SHA",
  110.                 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
  111.                 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
  112.                 "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
  113.                 "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
  114.                 "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
  115.                 "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
  116.                 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
  117.                 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
  118.                 "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
  119.                 "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
  120.                 "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
  121.                 "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
  122.                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
  123.                 "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
  124.                 "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
  125.                 "TLS_DH_anon_WITH_AES_256_CBC_SHA",
  126.                 "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
  127.                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
  128.                 "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
  129.                 "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
  130.                 "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
  131.                 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
  132.                 "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
  133.                 "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
  134.                 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
  135.                 "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
  136.                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
  137.                 "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
  138.                 "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
  139.                 "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
  140.                 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
  141.                 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
  142.                 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
  143.                 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
  144.                 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
  145.                 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
  146.                 "TLS_ECDHE_RSA_WITH_NULL_SHA",
  147.                 "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
  148.                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
  149.                 "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
  150.                 "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
  151.                 "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
  152.                 "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
  153.                 "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
  154.                 "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
  155.                 "TLS_ECDH_ECDSA_WITH_NULL_SHA",
  156.                 "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
  157.                 "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
  158.                 "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
  159.                 "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
  160.                 "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
  161.                 "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
  162.                 "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
  163.                 "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
  164.                 "TLS_ECDH_RSA_WITH_NULL_SHA",
  165.                 "TLS_ECDH_RSA_WITH_RC4_128_SHA",
  166.                 "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
  167.                 "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
  168.                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
  169.                 "TLS_ECDH_anon_WITH_NULL_SHA",
  170.                 "TLS_ECDH_anon_WITH_RC4_128_SHA",
  171.                 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
  172.                 "TLS_FALLBACK_SCSV",
  173.                 "TLS_PSK_WITH_3DES_EDE_CBC_SHA",
  174.                 "TLS_PSK_WITH_AES_128_CBC_SHA",
  175.                 "TLS_PSK_WITH_AES_256_CBC_SHA",
  176.                 "TLS_PSK_WITH_RC4_128_SHA",
  177.                 "TLS_RSA_WITH_AES_128_CBC_SHA",
  178.                 "TLS_RSA_WITH_AES_128_CBC_SHA256",
  179.                 "TLS_RSA_WITH_AES_128_GCM_SHA256",
  180.                 "TLS_RSA_WITH_AES_256_CBC_SHA",
  181.                 "TLS_RSA_WITH_AES_256_CBC_SHA256",
  182.                 "TLS_RSA_WITH_AES_256_GCM_SHA384",
  183.                 "TLS_RSA_WITH_NULL_SHA256")
  184.                 .where(new Cube.Selection<String>() {
  185.                     @Override
  186.                     public boolean predicate(String item, int index) {
  187.                         return item.startsWith(type);
  188.                     }
  189.                 });
  190.     }
  191.  
  192.     Socket configCipherSuites(Socket socket) {
  193.         if (!(socket instanceof SSLSocket)) return socket;
  194.         SSLSocket sslsocket = (SSLSocket) socket;
  195.         // 處理不合的cipher suite
  196.         Set<String> suites = getCipherSuites().toSet();
  197.         while (suites.size() > 0) {
  198.             try {
  199.                 sslsocket.setEnabledCipherSuites(suites.toArray(new String[suites.size()]));
  200.                 break;
  201.             } catch (Throwable e) {
  202.                 String message = e.getMessage();
  203.                 for (String cipher : suites) {
  204.                     if (message.toLowerCase().contains(cipher.toLowerCase())) {
  205.                         suites.remove(cipher);
  206.                         System.out.printf("Cipher suite %s has been removed.%n", cipher);
  207.                         break;
  208.                     }
  209.                 }
  210.             }
  211.         }
  212.         return socket;
  213.     }
  214. }
Gradle: error: unmappable character for encoding

Sometimes error occurs on gradle build cause of file encoding




Solved: add gradle vm option as below:

  1. -Dfile.encoding=utf-8
Gradle: generate java source folder
  1. task createSourceFolder {
  2.     sourceSets*.java.srcDirs*.each { it.mkdirs() }
  3.     sourceSets*.resources.srcDirs*.each { it.mkdirs() }
  4. }
Dynamic add classpath path
  1. public void appendClasspath(String path) throws NoSuchMethodException, MalformedURLException, InvocationTargetException, IllegalAccessException {
  2.     Path externalResourcesFolder = Paths.get(path);
  3.     ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
  4.     if (sysLoader instanceof URLClassLoader) {
  5.         Class<URLClassLoader> sysLoaderClass = URLClassLoader.class;
  6.  
  7.         // Use reflection to invoke the private addURL method
  8.         Method method = sysLoaderClass.getDeclaredMethod("addURL", URL.class);
  9.         method.setAccessible(true);
  10.         method.invoke(sysLoader, externalResourcesFolder.toUri().toURL());
  11.  
  12.         System.out.printf("classpath: %s loaded%n", path);
  13.     }
  14. }