├── src ├── test │ ├── resources │ │ └── config │ │ │ └── application-integrationtest.yml │ └── java │ │ └── com │ │ └── jfcorugedo │ │ ├── rserver │ │ ├── engine │ │ │ ├── RConnectionFactoryTest.java │ │ │ ├── REngineStdOutCallbackTest.java │ │ │ ├── JRIEngineProviderServiceTest.java │ │ │ ├── JRIEngineProviderServiceIT.java │ │ │ ├── RServeEngineProviderServiceIT.java │ │ │ └── RServeEngineProviderServiceTest.java │ │ ├── generalization │ │ │ └── algorithm │ │ │ │ ├── KolmogorovSmirnovTestImplIT.java │ │ │ │ ├── KolmogorovSmirnovTestImplTest.java │ │ │ │ └── ChiSquareTestImplTest.java │ │ ├── service │ │ │ ├── RServiceImplIT.java │ │ │ ├── RControllerTest.java │ │ │ └── RServiceImplTest.java │ │ └── common │ │ │ └── collection │ │ │ └── CollectionUtilsTest.java │ │ └── sample │ │ └── rjava │ │ ├── SimpleArithmeticCalculationsIT.java │ │ └── UseREngineInFrontOfJRIEngineIT.java └── main │ ├── java │ └── com │ │ └── jfcorugedo │ │ └── rserver │ │ ├── engine │ │ ├── REngineException.java │ │ ├── RConnectionFactory.java │ │ ├── REngineStdOutCallback.java │ │ ├── REngineProviderService.java │ │ ├── JRIEngineProviderService.java │ │ └── RServeEngineProviderService.java │ │ ├── exception │ │ ├── BadRequestException.java │ │ └── DimensionMismatchException.java │ │ ├── generalization │ │ └── algorithm │ │ │ ├── ChiSquareTest.java │ │ │ ├── KolmogorovSmirnovTest.java │ │ │ ├── KolmogorovSmirnovTestImpl.java │ │ │ └── ChiSquareTestImpl.java │ │ ├── Application.java │ │ ├── service │ │ ├── RController.java │ │ ├── RService.java │ │ └── RServiceImpl.java │ │ ├── common │ │ └── collection │ │ │ └── CollectionUtils.java │ │ └── metrics │ │ └── DatadogReporterConfig.java │ └── resources │ ├── config │ ├── application-local.yml │ └── application.yml │ └── logback.xml ├── .buildpacks ├── Procfile ├── .gitignore ├── rserve.conf ├── rserve_local.conf ├── init.r ├── .travis.yml ├── LICENSE ├── blockFunction.r ├── README.md └── pom.xml /src/test/resources/config/application-integrationtest.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 0 -------------------------------------------------------------------------------- /.buildpacks: -------------------------------------------------------------------------------- 1 | http://github.com/virtualstaticvoid/heroku-buildpack-r.git#cedar-14 2 | https://github.com/heroku/heroku-buildpack-java.git -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: $JAVA_HOME/bin/java -Dserver.port=$PORT -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE -Djava.library.path=/app/vendor/R/lib/R/library/rJava/jri/ -jar target/rjavaserver.jar 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | /.springBeans 10 | /.factorypath 11 | -------------------------------------------------------------------------------- /rserve.conf: -------------------------------------------------------------------------------- 1 | # See http://www.rforge.net/Rserve/doc.html#conf for configuration details 2 | 3 | remote enable 4 | auth disable 5 | #We can also load R script here 6 | source /app/blockFunction.r 7 | encoding utf8 8 | interactive no 9 | -------------------------------------------------------------------------------- /rserve_local.conf: -------------------------------------------------------------------------------- 1 | # See http://www.rforge.net/Rserve/doc.html#conf for configuration details 2 | 3 | remote enable 4 | auth disable 5 | #We can also load R script here 6 | source /Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.r 7 | encoding utf8 8 | interactive no 9 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/REngineException.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | public class REngineException extends RuntimeException{ 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public REngineException(String message, Throwable cause) { 8 | super(message, cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local.yml: -------------------------------------------------------------------------------- 1 | rJavaServer: 2 | rengine: 3 | blockFunction: source("/Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.R") 4 | rserveConf: /Users/jfcorugedo/Documents/git/RJavaServer/rserve_local.conf 5 | rexe: /Library/Frameworks/R.framework/Versions/3.2/Resources/bin/exec/R 6 | enableJRI: false 7 | metrics: 8 | period: 10 -------------------------------------------------------------------------------- /src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | endpoints: 2 | shutdown: 3 | enabled: true 4 | beans: 5 | sensitive: true 6 | server: 7 | port: 8080 8 | rJavaServer: 9 | rengine: 10 | blockFunction: source("/app/blockFunction.R") 11 | rserveConf: /app/rserve.conf 12 | rexe: /app/vendor/R/lib/R/bin/R 13 | metrics: 14 | apiKey: cf72a3a58261d378f033fb6b842a8601 15 | host: rJavaServer.dev 16 | period: 20 17 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value=HttpStatus.BAD_REQUEST) 7 | public class BadRequestException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public BadRequestException(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/exception/DimensionMismatchException.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value=HttpStatus.BAD_REQUEST) 7 | public class DimensionMismatchException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | 12 | public DimensionMismatchException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /init.r: -------------------------------------------------------------------------------- 1 | # 2 | # Example R code to install packages 3 | # See http://cran.r-project.org/doc/manuals/R-admin.html#Installing-packages for details 4 | # 5 | 6 | ########################################################### 7 | # Update this line with the R packages to install: 8 | 9 | my_packages = c("blockTools","rJava","Rserve","plyr") 10 | 11 | ########################################################### 12 | 13 | install_if_missing = function(p) { 14 | if (p %in% rownames(installed.packages()) == FALSE) { 15 | install.packages(p, dependencies = TRUE) 16 | } 17 | else { 18 | cat(paste("Skipping already installed package:", p, "\n")) 19 | } 20 | } 21 | invisible(sapply(my_packages, install_if_missing)) 22 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/RConnectionFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.times; 5 | import static org.mockito.Mockito.verify; 6 | 7 | import org.junit.Test; 8 | import org.rosuda.REngine.REngine; 9 | 10 | public class RConnectionFactoryTest { 11 | 12 | private RConnectionFactory connectionFactory = new RConnectionFactory(); 13 | 14 | @Test 15 | public void releaseConnectionTriesToCloseConnection() { 16 | 17 | REngine engine = mock(REngine.class); 18 | 19 | connectionFactory.releaseConnection(engine); 20 | 21 | verify(engine, times(1)).close(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/RConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import org.rosuda.REngine.REngine; 4 | import org.rosuda.REngine.Rserve.RConnection; 5 | import org.rosuda.REngine.Rserve.RserveException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class RConnectionFactory { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(RConnectionFactory.class); 14 | 15 | public RConnection getConnection() throws RserveException { 16 | return new RConnection(); 17 | } 18 | 19 | public void releaseConnection(REngine engine) { 20 | if(engine != null && !engine.close()) { 21 | LOGGER.warn("Unexpected error closing RServe connection"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: mvn -B verify 5 | after_success: mvn coveralls:report 6 | deploy: 7 | provider: heroku 8 | app: rjavaserver 9 | api_key: 10 | secure: HbF8N92OYm1Qe40ZzJrHoujblIbx7LIAzd1MlSS4CV6SPEVEtHJ0ee6u/zOUeJBBgvE4/6tiuq0bFu4pcBamYofgRoFmpVB5vwOVl/bqeFx/3vohkpgs0IVSlvGCH/bUW/yCwP58zt1UhzLztMigKwRJFj0vczDcBOLaNdVdeXRFTutnBcPcEUHt5umP4i4tNpm7HLv7gQrg5Xc524k98WKdz+RiTSUDVpFRfCYvFZ06hB8DDQgvsTCmvtXdpHDjvj38z2iU0xN0xyCziYrWGCZHvot5nnKCxm2j5snKSEnWS5iLOZh72tfyFQ3jiDBWXOiz+kQgX5/3OzZ1XdFWxM3l2w0JtG6fgtFaLkz2OSr8jRf9jjKXAD0/NeeFlpy3JtT0wJbN7ozeAFLn5n7wYikhLVhGvcwXlQeRR0OEUMRfifvebRg3kAOlUsTE6iZf8cZC8ZiVkq3i7wdRb4ZGdYGmAZE6wNHSsWb79I697jMKusUM3e6+1RPJvYFNPGlylFeqH4EsL3DaXfa7d1nA6XWmOR6z0rH7Y7uLc/ePDNTX90ZE88RxvWnx6XZnpNCbcqwOhLh8Kcv9jaE1Cao7BMeQDkKRm0lGNLDNU2Nu08Yqks8T+D+x1DiIGiy8CqrsibYZLGOn9/fma/cm63xw3Y1DVeS6eeOb+bufHOszqrk= 11 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | /** 4 | * This component executes Chi-Square test over two samples in order to compare them. 5 | * 6 | * 7 | * @author jfcorugedo 8 | * 9 | */ 10 | public interface ChiSquareTest { 11 | 12 | double DEFAULT_CONFIDENCE_LEVEL = 0.05; 13 | 14 | /** 15 | * This test evaluates the null hypothesis that the two list of observed counts 16 | * conform to the same frequency distribution. 17 | * 18 | * @param observed1 19 | * @param observed2 20 | * @return true if the null hypothesis can be accepted 21 | */ 22 | boolean areGeneralizable(long[] observed1, long[] observed2); 23 | 24 | /** 25 | * It returns a string representation of the algorithm used to compare the given samples. 26 | * 27 | * @return 28 | */ 29 | String getAlgorithm(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/REngineStdOutCallback.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import org.rosuda.REngine.REngine; 4 | import org.rosuda.REngine.REngineCallbacks; 5 | import org.rosuda.REngine.REngineOutputInterface; 6 | import org.slf4j.Logger; 7 | 8 | public class REngineStdOutCallback implements REngineCallbacks, REngineOutputInterface{ 9 | 10 | private Logger logger; 11 | 12 | public REngineStdOutCallback(Logger logger) { 13 | this.logger = logger; 14 | } 15 | 16 | @Override 17 | public void RWriteConsole(REngine eng, String text, int oType) {//NOSONAR 18 | if(oType == 0) { 19 | logger.info(text); 20 | } else { 21 | logger.warn(text); 22 | } 23 | } 24 | 25 | @Override 26 | public void RShowMessage(REngine eng, String text) {//NOSONAR 27 | logger.error(text); 28 | } 29 | 30 | @Override 31 | public void RFlushConsole(REngine eng) {//NOSONAR 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | /** 4 | * This component executes Two-sample Kolmogorov–Smirnov test over two samples in order to 5 | * know if both of them are equivalent and generalizable 6 | * 7 | * @see https://en.wikipedia.org/wiki/Kolmogorov–Smirnov_test 8 | * @author jfcorugedo 9 | * 10 | */ 11 | public interface KolmogorovSmirnovTest { 12 | 13 | double CRITICAL_VALUE_0_05 = 1.36; 14 | double CRITICAL_VALUE_0_01 = 1.63; 15 | 16 | /** 17 | * This test try to test if the null hypothesis is truth or not. 18 | * 19 | * The null hypothesis is that the samples are drawn from the same distribution. 20 | * 21 | */ 22 | boolean areGeneralizable(double[] x, double[] y); 23 | 24 | /** 25 | * It returns a string representation of the algorithm used to compare the given samples. 26 | * 27 | * @return 28 | */ 29 | String getAlgorithm(); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Juan Fernandez-Corugedo Igual 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImpl.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | import java.util.Arrays; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Component; 10 | 11 | import com.codahale.metrics.annotation.Timed; 12 | import com.jfcorugedo.rserver.service.RService; 13 | 14 | @Component 15 | public class KolmogorovSmirnovTestImpl implements KolmogorovSmirnovTest { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(KolmogorovSmirnovTestImpl.class); 18 | 19 | /** Name of the algorithm used to compare two samples */ 20 | private static final String KS_TEST = "K-S test"; 21 | 22 | @Inject 23 | private RService rService; 24 | 25 | /** 26 | * This test try to test if the null hypothesis is truth or not. 27 | * 28 | * The null hypothesis is that the samples are drawn from the same distribution. 29 | * 30 | */ 31 | @Override 32 | @Timed 33 | public boolean areGeneralizable(double[] x, double[] y){ 34 | 35 | if(LOGGER.isDebugEnabled()) { 36 | LOGGER.debug("Calculating K-S test of \nx: {}\ny: {}", Arrays.toString(x), Arrays.toString(y)); 37 | } 38 | 39 | double pValue = rService.kolmogorovSmirnovTest(x, y); 40 | 41 | if(LOGGER.isDebugEnabled()) { 42 | LOGGER.debug("K-S pValue calculated: {}", pValue); 43 | } 44 | 45 | //Accept null hypothesis: Both samples come from the same distribution 46 | return pValue > 0.05; 47 | } 48 | 49 | @Override 50 | public String getAlgorithm() { 51 | 52 | return KS_TEST; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/REngineStdOutCallbackTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static org.mockito.Matchers.anyString; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.never; 6 | import static org.mockito.Mockito.times; 7 | import static org.mockito.Mockito.verify; 8 | 9 | import org.junit.Test; 10 | import org.rosuda.REngine.REngine; 11 | import org.slf4j.Logger; 12 | 13 | public class REngineStdOutCallbackTest { 14 | 15 | @Test 16 | public void testPrintInfoMessage() { 17 | 18 | Logger loggerMock = mock(Logger.class); 19 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock); 20 | 21 | stdoutCallback.RWriteConsole(mock(REngine.class), "Something has happened", 0); 22 | 23 | verify(loggerMock, times(1)).info("Something has happened"); 24 | } 25 | 26 | @Test 27 | public void testPrintWarnMessage() { 28 | 29 | Logger loggerMock = mock(Logger.class); 30 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock); 31 | 32 | stdoutCallback.RWriteConsole(mock(REngine.class), "Something goes wrong", 1); 33 | 34 | verify(loggerMock, times(1)).warn("Something goes wrong"); 35 | } 36 | 37 | @Test 38 | public void testPrintErrorMessage() { 39 | 40 | Logger loggerMock = mock(Logger.class); 41 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock); 42 | 43 | stdoutCallback.RShowMessage(mock(REngine.class), "Something goes really wrong"); 44 | 45 | verify(loggerMock, times(1)).error("Something goes really wrong"); 46 | } 47 | 48 | @Test 49 | public void testFlushConsole() { 50 | 51 | Logger loggerMock = mock(Logger.class); 52 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock); 53 | 54 | stdoutCallback.RFlushConsole(mock(REngine.class)); 55 | 56 | verify(loggerMock, never()).info(anyString()); 57 | verify(loggerMock, never()).warn(anyString()); 58 | verify(loggerMock, never()).error(anyString()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/Application.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.core.env.SimpleCommandLinePropertySource; 8 | 9 | import com.ryantenney.metrics.spring.config.annotation.EnableMetrics; 10 | 11 | @SpringBootApplication 12 | @EnableMetrics 13 | public class Application { 14 | 15 | private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); 16 | 17 | public static void main(String[] args) { 18 | LOGGER.info("\n****************\n* rJava Server *\n****************\n"); 19 | 20 | SpringApplication app = new SpringApplication(Application.class); 21 | app.setShowBanner(false); 22 | SimpleCommandLinePropertySource source = new SimpleCommandLinePropertySource(args); 23 | 24 | // Check if the selected profile has been set as argument. 25 | // if not the development profile will be added 26 | addDefaultProfile(app, source); 27 | 28 | 29 | 30 | app.run(args); 31 | } 32 | 33 | /** 34 | * Set a default profile if it has not been set 35 | */ 36 | private static void addDefaultProfile(SpringApplication app, SimpleCommandLinePropertySource source) { 37 | if (!source.containsProperty("spring.profiles.active")) { 38 | app.setAdditionalProfiles("local"); 39 | LOGGER.info("Staring application with profiles: local"); 40 | } else { 41 | LOGGER.info("Staring application with profiles: {}", source.getProperty("spring.profiles.active")); 42 | } 43 | } 44 | 45 | /** 46 | * This method is needed because Sonar doesn't allow public constructors in a class with only static methods, 47 | * but SpringBoot needs a public constructor in this class. 48 | * This method should not be invoked 49 | */ 50 | public void avoidSonarViolation(){ 51 | LOGGER.warn("This method should not be invoked"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTestImpl.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import com.codahale.metrics.annotation.Timed; 8 | import com.jfcorugedo.rserver.exception.DimensionMismatchException; 9 | 10 | @Component 11 | public class ChiSquareTestImpl implements ChiSquareTest { 12 | 13 | public static final String CHI_TEST = "Chi-Square test"; 14 | 15 | private org.apache.commons.math3.stat.inference.ChiSquareTest chiTest = new org.apache.commons.math3.stat.inference.ChiSquareTest(); 16 | 17 | @Override 18 | @Timed 19 | public boolean areGeneralizable(long[] observed1, long[] observed2) { 20 | 21 | if(observed1.length != observed2.length) { 22 | throw new DimensionMismatchException(format("Observed samples have different dimension: %d != %d", observed1.length, observed2.length)); 23 | } 24 | 25 | if(isBinary(observed1, observed2) && (observed1[0] == 0 || observed1[1] == 0)){ 26 | return true; 27 | } 28 | 29 | return !chiTest.chiSquareTestDataSetsComparison(observed1, observed2, DEFAULT_CONFIDENCE_LEVEL); 30 | } 31 | 32 | /** 33 | * it returns true when there are only two possible values on each sample. 34 | * 35 | * For instance, given two samples with these samples: 36 | * 37 | *
38 | 	 * - "true":10, "false":20
39 | 	 * - "true":2, "false":3
40 | 	 * 
41 | * 42 | * This method will return true, because there are only two possible values: "true" or "false". 43 | * 44 | * On the other hand, given these samples: 45 | * 46 | *
47 | 	 * - "MAD":10, "LON":23, "NY":45
48 | 	 * - "MAD":2, "LON":4, "NY":7
49 | 	 * 
50 | * 51 | * This method will return flase, because there three possible values in each sample 52 | * 53 | * @param observed1 54 | * @param observed2 55 | * @return 56 | */ 57 | private boolean isBinary(long[] observed1, long[] observed2) { 58 | 59 | return observed1.length == 2 && observed2.length == 2; 60 | } 61 | 62 | @Override 63 | public String getAlgorithm() { 64 | 65 | return CHI_TEST; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImplIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.stream.IntStream; 6 | 7 | import javax.inject.Inject; 8 | 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.boot.test.SpringApplicationConfiguration; 13 | import org.springframework.boot.test.WebIntegrationTest; 14 | import org.springframework.test.context.ActiveProfiles; 15 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 16 | 17 | import com.jfcorugedo.rserver.Application; 18 | 19 | @Ignore("These tests requires R environment installed") 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @SpringApplicationConfiguration(classes = Application.class) 22 | @WebIntegrationTest 23 | @ActiveProfiles(profiles={"local", "integrationtest"}) 24 | public class KolmogorovSmirnovTestImplIT { 25 | 26 | @Inject 27 | private KolmogorovSmirnovTest ksTest; 28 | 29 | @Test 30 | public void areTwoEqualSamplesGeneralizable() { 31 | 32 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0}; 33 | 34 | boolean result = ksTest.areGeneralizable(sample, sample); 35 | 36 | assertThat(result).isTrue(); 37 | } 38 | 39 | @Test 40 | public void areTwoDifferentSamplesGeneralizable() { 41 | 42 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0}; 43 | double[] different = new double[]{-1.0, -2.0, -3.0, -4.0}; 44 | 45 | boolean result = ksTest.areGeneralizable(sample, different); 46 | 47 | assertThat(result).isFalse(); 48 | } 49 | 50 | @Test 51 | public void getAlgorithmName() { 52 | 53 | String algorithm = ksTest.getAlgorithm(); 54 | 55 | assertThat(algorithm).isEqualTo("K-S test"); 56 | } 57 | 58 | @Test 59 | public void testSameLargeSamples() { 60 | 61 | double[] sample1 = IntStream.range(0, 10000).asDoubleStream().toArray(); 62 | double[] sample2 = IntStream.range(0, 10000).asDoubleStream().toArray(); 63 | 64 | boolean isGeneralizable = ksTest.areGeneralizable(sample1, sample2); 65 | 66 | assertThat(isGeneralizable).isTrue(); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Matchers.any; 5 | import static org.mockito.Mockito.when; 6 | 7 | import java.util.stream.IntStream; 8 | 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.mockito.junit.MockitoJUnit; 14 | import org.mockito.junit.MockitoRule; 15 | 16 | import com.jfcorugedo.rserver.service.RService; 17 | 18 | public class KolmogorovSmirnovTestImplTest { 19 | 20 | @Rule 21 | public MockitoRule rule = MockitoJUnit.rule(); 22 | 23 | @Mock 24 | private RService rService; 25 | 26 | @InjectMocks 27 | private KolmogorovSmirnovTestImpl ksTest = new KolmogorovSmirnovTestImpl(); 28 | 29 | @Test 30 | public void areTwoEqualSamplesGeneralizable() { 31 | 32 | setKSPValue(1.0); 33 | 34 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0}; 35 | 36 | boolean result = ksTest.areGeneralizable(sample, sample); 37 | 38 | assertThat(result).isTrue(); 39 | } 40 | 41 | @Test 42 | public void areTwoDifferentSamplesGeneralizable() { 43 | 44 | setKSPValue(0.03); 45 | 46 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0}; 47 | double[] different = new double[]{-1.0, -2.0, -3.0, -4.0}; 48 | 49 | boolean result = ksTest.areGeneralizable(sample, different); 50 | 51 | assertThat(result).isFalse(); 52 | } 53 | 54 | @Test 55 | public void getAlgorithmName() { 56 | 57 | String algorithm = ksTest.getAlgorithm(); 58 | 59 | assertThat(algorithm).isEqualTo("K-S test"); 60 | } 61 | 62 | @Test 63 | public void testSameSamples() { 64 | 65 | setKSPValue(0.03); 66 | 67 | double[] sample1 = IntStream.range(0, 100).asDoubleStream().toArray(); 68 | double[] sample2 = IntStream.range(0, 100).asDoubleStream().toArray(); 69 | 70 | boolean isGeneralizable = ksTest.areGeneralizable(sample1, sample2); 71 | 72 | assertThat(isGeneralizable).isFalse(); 73 | } 74 | 75 | private void setKSPValue(double pValue) { 76 | 77 | when(rService.kolmogorovSmirnovTest(any(double[].class), any(double[].class))).thenReturn(pValue); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/REngineProviderService.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import java.util.List; 4 | 5 | import org.rosuda.REngine.REXP; 6 | import org.rosuda.REngine.REXPDouble; 7 | import org.rosuda.REngine.REXPInteger; 8 | import org.rosuda.REngine.REXPString; 9 | 10 | /** 11 | * Interface used to obtain access to the low-level engine 12 | */ 13 | public interface REngineProviderService { 14 | 15 | /** 16 | * Executes R block function against a list of values. 17 | * 18 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf 19 | * @param ids Identifiers associated to each value 20 | * @param values Values used to perform the matching 21 | * @return An object containing all the IDs grouped in pairs based on the distance of each one 22 | */ 23 | REXP blockFunction(REXPInteger ids, REXPDouble... values); 24 | 25 | /** 26 | * Executes R block function against a list of discrete (non-numeric) values. 27 | * 28 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf 29 | * @param ids Identifiers associated to each value 30 | * @param values Values that must be grouped into pairs 31 | * @return An object containing all the IDs grouped in pairs based on the distance of eachone 32 | */ 33 | REXP blockDiscreteFunction(REXPInteger ids, REXPString... values); 34 | 35 | /** 36 | * Executes R block function against a list of discrete (non-numeric) and continuous values. 37 | * 38 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf 39 | * @param ids Identifiers associated to each value 40 | * @param discreteValues List of discrete values that must be used to group elements 41 | * @param continuousValues List of continuous values that must be used to group elements 42 | * @return An object containing all the IDs grouped in pairs based on the distance of eachone 43 | */ 44 | REXP blockGeneralFunction(REXPInteger ids, List discreteValues, List continuousValues); 45 | 46 | /** 47 | * Calculates the sqare root of a given number 48 | * @param number 49 | * @return 50 | */ 51 | double sqrt(double number); 52 | 53 | /** 54 | * Perform a two-sample Kolmogorov-Smirnov test. 55 | * 56 | * @param x A numeric vector of data values. 57 | * @param y A numeric vector of data values. 58 | * @return p-value associated with the null hypothesis that x and y come from the same distribution (are equivalent) 59 | */ 60 | double ksTest(REXPDouble x, REXPDouble y); 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/sample/rjava/SimpleArithmeticCalculationsIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.sample.rjava; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import org.junit.BeforeClass; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | import org.rosuda.JRI.REXP; 10 | import org.rosuda.JRI.Rengine; 11 | 12 | /** 13 | * In order to make these test work, two parameters must be configured: 14 | * 15 | * Inside VM args, add: 16 | * -Djava.library.path=/Library/Frameworks/R.framework/Versions/3.2/Resources/library/rJava/jri/ 17 | * 18 | * Export an environment variable with this value: 19 | * 20 | * R_HOME=/Library/Frameworks/R.framework/Resources 21 | * 22 | * @author jfcorugedo 23 | * 24 | */ 25 | @Ignore("Maven can't execute this tests because they require a R environmet installed") 26 | public class SimpleArithmeticCalculationsIT { 27 | 28 | private static Rengine engine = null; 29 | 30 | @BeforeClass 31 | public static void setUpR() { 32 | // just making sure we have the right version of everything 33 | if (!Rengine.versionCheck()) { 34 | System.err.println("** Version mismatch - Java files don't match library version."); 35 | fail(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion())); 36 | } 37 | 38 | // Enables debug traces 39 | Rengine.DEBUG = 1; 40 | 41 | System.out.println("Creating Rengine (with arguments)"); 42 | // 1) we pass the arguments from the command line 43 | // 2) we won't use the main loop at first, we'll start it later 44 | // (that's the "false" as second argument) 45 | // 3) no callback class will be used 46 | engine = new Rengine(new String[] { "--no-save" }, false, null); 47 | System.out.println("Rengine created, waiting for R"); 48 | // the engine creates R is a new thread, so we should wait until it's 49 | // ready 50 | if (!engine.waitForR()) { 51 | fail("Cannot load R"); 52 | } 53 | } 54 | 55 | @Test 56 | public void testMean() throws Exception{ 57 | 58 | 59 | /* 60 | * High-level API - do not use RNI methods unless there is no other way 61 | * to accomplish what you want 62 | */ 63 | engine.eval("rVector=c(1,2,3,4,5)"); 64 | REXP result = engine.eval("meanVal=mean(rVector)"); 65 | // generic vectors are RVector to accomodate names 66 | assertThat(result.asDouble()).isEqualTo(3.0); 67 | } 68 | 69 | @Test 70 | public void testSqrt() { 71 | REXP result = engine.eval("sqrt(36)"); 72 | assertThat(result.asDouble()).isEqualTo(6.0); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/service/RController.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import static org.springframework.web.bind.annotation.RequestMethod.POST; 4 | 5 | import java.util.List; 6 | 7 | import javax.inject.Inject; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import com.codahale.metrics.annotation.Timed; 18 | import com.jfcorugedo.rserver.exception.BadRequestException; 19 | import com.jfcorugedo.rserver.generalization.algorithm.KolmogorovSmirnovTest; 20 | 21 | 22 | @RestController 23 | public class RController { 24 | 25 | public static final Logger LOGGER = LoggerFactory.getLogger(RController.class); 26 | 27 | @Inject 28 | private RService rService; 29 | 30 | @Inject 31 | private KolmogorovSmirnovTest ksTest; 32 | 33 | @RequestMapping(value="/stratification/continuous/group", method=POST) 34 | public ResponseEntity> block(@RequestBody(required=false) List data) { 35 | if(data != null) { 36 | return new ResponseEntity<>(rService.groupValues(data), HttpStatus.CREATED); 37 | } else { 38 | throw new BadRequestException("This service needs all the values of the stratification variable"); 39 | } 40 | } 41 | 42 | @RequestMapping(value="/stratification/discrete/group", method=POST) 43 | public ResponseEntity> blockDiscrete(@RequestBody(required=false) List data) { 44 | if(data != null) { 45 | return new ResponseEntity<>(rService.groupDiscreteValues(data), HttpStatus.CREATED); 46 | } else { 47 | throw new BadRequestException("This service needs all the values of the stratification variable"); 48 | } 49 | } 50 | 51 | @RequestMapping(value="/ks-test", method=POST) 52 | @Timed 53 | public ResponseEntity areGeneralizable(@RequestBody List values) { 54 | 55 | if(values.size() == 2) { 56 | boolean areGeneralizable = ksTest.areGeneralizable(values.get(0), values.get(1)); 57 | 58 | return new ResponseEntity<>(String.format("%s: %b", ksTest.getAlgorithm(), areGeneralizable), HttpStatus.CREATED); 59 | } else { 60 | return new ResponseEntity<>("This algorithm needs two different list of values", HttpStatus.BAD_REQUEST); 61 | } 62 | } 63 | 64 | @RequestMapping(value="/sqrt", method=POST) 65 | public ResponseEntity sqrt(@RequestBody Double number) { 66 | 67 | return new ResponseEntity<>(rService.sqrt(number), HttpStatus.CREATED); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/service/RService.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import java.util.List; 4 | 5 | 6 | public interface RService { 7 | 8 | /** 9 | * Groups the given values in pairs based on the correlation between values. 10 | * 11 | * For instance, given the following values: 12 | * 13 | * {1.0, 2.0, 2.0, 1.0} 14 | * 15 | * This function will return 16 | * 17 | * [{0,3}, {1,2}] 18 | * 19 | * So the first element in the values array must be paired with the fourth element of the array. 20 | * Similarly, the second element of the array must be paired with the third element. 21 | * 22 | * @param values Values to be paired 23 | * @return List of pairs. Each element in the list will have to indexes that point at the elements 24 | * of the input array that must be paired 25 | */ 26 | List groupValues(List values); 27 | 28 | /** 29 | * Groups the given values in pairs based on the correlation between values. 30 | * 31 | * For instance, given the following values: 32 | * 33 | * {"A", "B", "B", "A"} 34 | * 35 | * This function will return 36 | * 37 | * [{0,3}, {1,2}] 38 | * 39 | * So the first element in the values array must be paired with the fourth element of the array. 40 | * Similarly, the second element of the array must be paired with the third element. 41 | * 42 | * @param values Values to be paired 43 | * @return List of pairs. Each element in the list will have to indexes that point at the elements 44 | * of the input array that must be paired 45 | */ 46 | List groupDiscreteValues(List values); 47 | 48 | /** 49 | * Groups the given values in pairs based on the correlation between values. 50 | * 51 | * For instance, given the following values: 52 | * 53 | * {1.0, 2.0, 3.0, 4.0}, {"A", "A", "B", "B"} 54 | * 55 | * This function will return 56 | * 57 | * [{0,3}, {1,2}] 58 | * 59 | * So the first element in the values array must be paired with the fourth element of the array. 60 | * Similarly, the second element of the array must be paired with the third element. 61 | * 62 | * @param values Values to be paired 63 | * @return List of pairs. Each element in the list will have to indexes that point at the elements 64 | * of the input array that must be paired 65 | */ 66 | List groupMultipleValues(List discreteValues, List continuousValues); 67 | 68 | /** 69 | * Computes the p-value, or observed significance level, of a two-sample Kolmogorov-Smirnov test 70 | * evaluating the null hypothesis that x and y are samples drawn from the same probability distribution. 71 | * 72 | * @param x 73 | * @param y 74 | * @return 75 | */ 76 | double kolmogorovSmirnovTest(double[] x, double[] y); 77 | 78 | 79 | /** 80 | * This method returns the square root of the given value 81 | * @param number 82 | * @return 83 | */ 84 | double sqrt(double number); 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/common/collection/CollectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.common.collection; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | public final class CollectionUtils { 11 | 12 | private CollectionUtils(){ 13 | 14 | } 15 | 16 | public static List arrayToList(T[] array){ 17 | return Arrays.stream(array).collect(Collectors.toList());//NOSONAR: This stream doesn't need to be closed 18 | } 19 | 20 | /** 21 | * Return true if this map is null or it's empty. 22 | * 23 | * @param map 24 | * @return 25 | */ 26 | public static boolean isEmpty(Map map) { 27 | return map == null || map.isEmpty(); 28 | } 29 | 30 | public static boolean isEmpty(List list) { 31 | return list == null || list.isEmpty(); 32 | } 33 | 34 | /** 35 | * Return true if this map contains one value at least 36 | * 37 | * @param map 38 | * @return 39 | */ 40 | public static boolean isNotEmpty(Map map) { 41 | return !isEmpty(map); 42 | } 43 | 44 | public static boolean isNotEmpty(Collection collection) { 45 | return collection != null && !collection.isEmpty(); 46 | } 47 | 48 | /** 49 | * Converts the given list of Double objects into an array of primitive doubles. 50 | * 51 | * If the list is null or empty, an empty array will be returned 52 | * 53 | * @param inputList List of Double object to be converted in a primitive array 54 | * @return 55 | */ 56 | public static double[] listToPrimitiveArray(List inputList) { 57 | if(isEmpty(inputList)) { 58 | return new double[0]; 59 | } else { 60 | return inputList.stream().mapToDouble(Double::new).toArray(); 61 | } 62 | } 63 | 64 | /** 65 | * This method creates a new list and store in it all the objects contained in the 66 | * given list. 67 | * 68 | * @param targetList Source list used to create a new one 69 | * @return New list containing all the objects of the primitive list 70 | */ 71 | public static List cloneList(List targetList) { 72 | return targetList.stream().collect(Collectors.toList()); 73 | } 74 | 75 | /** 76 | * Transform a list of objects into an array of primitives 77 | * 78 | * @param listOfObject 79 | * @return 80 | */ 81 | public static double[] convertToPrimitives(List listOfObjects) { 82 | return listOfObjects.stream().mapToDouble(Double::doubleValue).toArray(); 83 | } 84 | 85 | /** 86 | * Creates a new list with the given elements 87 | * @param values 88 | * @return 89 | */ 90 | @SafeVarargs 91 | public static List newList(T... values) { 92 | List arrayList = new ArrayList<>(values.length); 93 | for(T element : values) { 94 | arrayList.add(element); 95 | } 96 | 97 | return arrayList; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/service/RServiceImplIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.Random; 7 | 8 | import javax.inject.Inject; 9 | 10 | import org.junit.Ignore; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.boot.test.SpringApplicationConfiguration; 16 | import org.springframework.boot.test.WebIntegrationTest; 17 | import org.springframework.test.context.ActiveProfiles; 18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 19 | 20 | import com.jfcorugedo.rserver.Application; 21 | 22 | @Ignore("These tests requires R environment installed") 23 | @RunWith(SpringJUnit4ClassRunner.class) 24 | @SpringApplicationConfiguration(classes = Application.class) 25 | @WebIntegrationTest 26 | @ActiveProfiles(profiles={"local", "integrationtest"}) 27 | public class RServiceImplIT { 28 | 29 | private static final Logger LOGGER = LoggerFactory.getLogger(RServiceImplIT.class); 30 | 31 | public static final double[] TEST_POPULATION = new double[20]; 32 | static{ 33 | TEST_POPULATION[0] = 10; 34 | TEST_POPULATION[1] = 5; 35 | TEST_POPULATION[2] = 20; 36 | TEST_POPULATION[3] = 12; 37 | TEST_POPULATION[4] = 4; 38 | TEST_POPULATION[5] = 11; 39 | TEST_POPULATION[6] = 23; 40 | TEST_POPULATION[7] = 0; 41 | TEST_POPULATION[8] = 12; 42 | TEST_POPULATION[9] = 6; 43 | TEST_POPULATION[10] = 6; 44 | TEST_POPULATION[11] = 11; 45 | TEST_POPULATION[12] = 14; 46 | TEST_POPULATION[13] = 13; 47 | TEST_POPULATION[14] = 16; 48 | TEST_POPULATION[15] = 18; 49 | TEST_POPULATION[16] = 19; 50 | TEST_POPULATION[17] = 12; 51 | TEST_POPULATION[18] = 0; 52 | TEST_POPULATION[19] = 4; 53 | } 54 | 55 | public static final double[] TEST_BIG_POPULATION = new double[200]; 56 | static{ 57 | for(int i = 0 ; i < 10 ; i++) { 58 | for(int j = 0 ; j < 20 ; j++) { 59 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j]; 60 | } 61 | } 62 | } 63 | 64 | @Inject 65 | private RServiceImpl service; 66 | 67 | @Test 68 | public void testBlockFunction() { 69 | 70 | for(int i = 0 ; i < 2 ; i++) { 71 | if(LOGGER.isInfoEnabled()) { 72 | LOGGER.info(Integer.toString(i)); 73 | } 74 | service.groupValues(newList(TEST_BIG_POPULATION)); 75 | } 76 | } 77 | 78 | @Test 79 | public void testBlockFunctionWithRandom() { 80 | 81 | for(int i = 0 ; i < 2 ; i++) { 82 | if(LOGGER.isInfoEnabled()) { 83 | LOGGER.info(Integer.toString(i)); 84 | } 85 | service.groupValues(newList(new Random().doubles(200).map(v -> v*20).toArray())); 86 | } 87 | } 88 | 89 | @Test 90 | public void ksTest() { 91 | 92 | double[] x = new double[]{ 1.0, 3.0, 50.0}; 93 | double[] y = new double[]{ 2.0, 4.0, 90.0, 34.1}; 94 | 95 | double pValue = service.kolmogorovSmirnovTest(x, y); 96 | 97 | assertThat(pValue).isGreaterThan(0.80); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTestImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.generalization.algorithm; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | import com.jfcorugedo.rserver.exception.DimensionMismatchException; 8 | 9 | public class ChiSquareTestImplTest { 10 | 11 | @Test 12 | public void areSimilarDistributionGeneralizable() { 13 | 14 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 15 | 16 | boolean result = chiSquareTest.areGeneralizable( 17 | new long[]{10, 20, 20, 30}, 18 | new long[]{100, 200, 300, 400} 19 | ); 20 | 21 | assertThat(result).isTrue(); 22 | } 23 | 24 | @Test 25 | public void areDifferentDistributionGeneralizable() { 26 | 27 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 28 | 29 | boolean result = chiSquareTest.areGeneralizable( 30 | new long[]{10, 20, 20, 30}, 31 | new long[]{200, 100, 300, 400} 32 | ); 33 | 34 | assertThat(result).isFalse(); 35 | } 36 | 37 | @Test 38 | public void compareTwoValuesWhenOneOfThemHasZeroFrequency() { 39 | 40 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 41 | 42 | boolean result = chiSquareTest.areGeneralizable( 43 | new long[]{0, 2000}, 44 | new long[]{0, 10} 45 | ); 46 | 47 | assertThat(result).isTrue(); 48 | } 49 | 50 | @Test 51 | public void secondSampleHasZeroFrequency() { 52 | 53 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 54 | 55 | boolean result = chiSquareTest.areGeneralizable( 56 | new long[]{10, 0}, 57 | new long[]{1, 0} 58 | ); 59 | 60 | assertThat(result).isTrue(); 61 | } 62 | 63 | @Test 64 | public void firstSampleHasZeroFrequency() { 65 | 66 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 67 | 68 | boolean result = chiSquareTest.areGeneralizable( 69 | new long[]{0, 2000}, 70 | new long[]{10, 10} 71 | ); 72 | 73 | assertThat(result).isTrue(); 74 | } 75 | 76 | @Test 77 | public void binaryFrequenciesWithoutAnyZero() { 78 | 79 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 80 | 81 | boolean result = chiSquareTest.areGeneralizable( 82 | new long[]{1000, 20000}, 83 | new long[]{100, 100} 84 | ); 85 | 86 | assertThat(result).isFalse(); 87 | } 88 | 89 | @Test(expected=DimensionMismatchException.class) 90 | public void compareTwoSamplesWithDifferentLength() { 91 | 92 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 93 | 94 | boolean result = chiSquareTest.areGeneralizable( 95 | new long[]{10, 20, 30}, 96 | new long[]{1, 2} 97 | ); 98 | 99 | assertThat(result).isTrue(); 100 | } 101 | 102 | @Test 103 | public void getAlgorithm() { 104 | 105 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl(); 106 | 107 | String algorithm = chiSquareTest.getAlgorithm(); 108 | 109 | assertThat(algorithm).isEqualTo("Chi-Square test"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/sample/rjava/UseREngineInFrontOfJRIEngineIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.sample.rjava; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import org.junit.BeforeClass; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | import org.rosuda.JRI.Rengine; 10 | import org.rosuda.REngine.REXP; 11 | import org.rosuda.REngine.REXPDouble; 12 | import org.rosuda.REngine.REngine; 13 | import org.rosuda.REngine.REngineStdOutput; 14 | import org.rosuda.REngine.RList; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | @Ignore("Maven can't execute this tests because they require a R environmet installed") 19 | public class UseREngineInFrontOfJRIEngineIT { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(UseREngineInFrontOfJRIEngineIT.class); 22 | 23 | private static REngine engine; 24 | 25 | @Test 26 | public void testAssignDataFrame() throws Exception{ 27 | REXPDouble rIds = new REXPDouble(new double[]{1d,2d}); 28 | REXPDouble rValues = new REXPDouble(new double[]{10000d,20000d}); 29 | 30 | RList data = new RList(); 31 | data.add(rIds); 32 | data.add(rValues); 33 | data.setKeyAt(0, "id"); 34 | data.setKeyAt(1, "values"); 35 | 36 | engine.assign("data", REXP.createDataFrame(data)); 37 | 38 | REXP result = engine.parseAndEval("blockFunction(data,c(\"id\"),c(\"values\"))"); 39 | 40 | 41 | System.out.println(result.toDebugString()); 42 | } 43 | 44 | @Test 45 | public void testMean() throws Exception{ 46 | 47 | 48 | /* 49 | * High-level API - do not use RNI methods unless there is no other way 50 | * to accomplish what you want 51 | */ 52 | engine.parseAndEval("rVector=c(1,2,3,4,5)"); 53 | REXP result = engine.parseAndEval("meanVal=mean(rVector)"); 54 | // generic vectors are RVector to accomodate names 55 | assertThat(result.asDouble()).isEqualTo(3.0); 56 | } 57 | 58 | @Test 59 | public void testSqrt() throws Exception{ 60 | REXP result = engine.parseAndEval("sqrt(36)"); 61 | assertThat(result.asDouble()).isEqualTo(6.0); 62 | } 63 | 64 | @BeforeClass 65 | public static void setUpR() throws Exception{ 66 | // just making sure we have the right version of everything 67 | if (!Rengine.versionCheck()) { 68 | System.err.println("** Version mismatch - Java files don't match library version."); 69 | fail(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion())); 70 | } 71 | 72 | // Enables debug traces 73 | Rengine.DEBUG = 1; 74 | 75 | System.out.println("Creating Rengine (with arguments)"); 76 | // 1) we pass the arguments from the command line 77 | // 2) we won't use the main loop at first, we'll start it later 78 | // (that's the "false" as second argument) 79 | // 3) no callback class will be used 80 | engine = REngine.engineForClass("org.rosuda.REngine.JRI.JRIEngine", new String[] { "--no-save" }, new REngineStdOutput(), false); 81 | System.out.println("Rengine created..."); 82 | 83 | REXP result = engine.parseAndEval("source(\"/Users/jfcorugedo/Documents/git/RJavaServer/src/test/resources/blockFunction.R\")"); 84 | if(result == null) { 85 | LOG.error("blockFunction is not loaded!"); 86 | } else { 87 | LOG.info("blockFunction loaded successfully"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | utf-8 7 | %d [%p] [%t] %c - %m%n 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | true 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/service/RControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.mockito.Matchers.any; 6 | import static org.mockito.Mockito.times; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.when; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.mockito.InjectMocks; 16 | import org.mockito.Mock; 17 | import org.mockito.junit.MockitoJUnit; 18 | import org.mockito.junit.MockitoRule; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.http.ResponseEntity; 21 | 22 | import com.jfcorugedo.rserver.exception.BadRequestException; 23 | import com.jfcorugedo.rserver.generalization.algorithm.KolmogorovSmirnovTest; 24 | 25 | public class RControllerTest { 26 | 27 | @Rule 28 | public MockitoRule rule = MockitoJUnit.rule(); 29 | 30 | @Mock 31 | private RService rService; 32 | 33 | @Mock 34 | private KolmogorovSmirnovTest ksTest; 35 | 36 | @InjectMocks 37 | private RController controller = new RController(); 38 | 39 | @Test(expected=BadRequestException.class) 40 | public void testThrowsExceptionWhenNullInput() { 41 | 42 | controller.block(null); 43 | } 44 | 45 | @Test 46 | public void testCallBlock() { 47 | 48 | List values = newList(new double[]{1d,2d}); 49 | controller.block(values); 50 | 51 | verify(rService, times(1)).groupValues(values); 52 | } 53 | 54 | @Test(expected=BadRequestException.class) 55 | public void testBlockDiscreteThrowsExceptionWhenNullInput() { 56 | 57 | controller.blockDiscrete(null); 58 | } 59 | 60 | @Test 61 | public void testCallBlockDiscrete() { 62 | 63 | List values = buildList(new String[]{"a", "b", "c"}); 64 | controller.blockDiscrete(values); 65 | 66 | verify(rService, times(1)).groupDiscreteValues(values); 67 | } 68 | 69 | @Test 70 | public void testCallSqrt() { 71 | 72 | controller.sqrt(4d); 73 | 74 | verify(rService, times(1)).sqrt(4d); 75 | } 76 | 77 | private List buildList(String[]... strings) { 78 | List result = new ArrayList(); 79 | for(String[] value : strings) { 80 | result.add(value); 81 | } 82 | return result; 83 | } 84 | 85 | @Test 86 | public void areGeneralizableCallsKSTest() { 87 | 88 | double[] sampleA = new double[]{1d, 2d}; 89 | double[] sampleB = new double[]{3d, 4d}; 90 | 91 | controller.areGeneralizable(newList(sampleA, sampleB)); 92 | 93 | verify(ksTest, times(1)).areGeneralizable(sampleA, sampleB); 94 | } 95 | 96 | @Test 97 | public void areGeneralizableRetursResult() { 98 | 99 | when(ksTest.areGeneralizable(any(), any())).thenReturn(true); 100 | when(ksTest.getAlgorithm()).thenReturn("K-S test"); 101 | 102 | ResponseEntity result = controller.areGeneralizable(newList(new double[]{1d, 2d}, new double[]{1d, 2d})); 103 | 104 | assertThat(result.getBody()).isEqualTo("K-S test: true"); 105 | } 106 | 107 | @Test 108 | public void areGeneralizableReturnsBadRequestIfInputDoesNotHaveEnoughValues() { 109 | 110 | ResponseEntity result = controller.areGeneralizable(newList(new double[]{1d, 2d})); 111 | 112 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); 113 | assertThat(result.getBody()).isEqualTo("This algorithm needs two different list of values"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/JRIEngineProviderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.mockito.Matchers.anyString; 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.mockito.junit.MockitoJUnit; 14 | import org.mockito.junit.MockitoRule; 15 | import org.rosuda.REngine.REXP; 16 | import org.rosuda.REngine.REXPDouble; 17 | import org.rosuda.REngine.REXPInteger; 18 | import org.rosuda.REngine.REXPString; 19 | import org.rosuda.REngine.REngine; 20 | 21 | public class JRIEngineProviderServiceTest { 22 | 23 | @Rule 24 | public MockitoRule rule = MockitoJUnit.rule(); 25 | 26 | @Mock 27 | private REngine engine; 28 | 29 | @InjectMocks 30 | private REngineProviderService service = new JRIEngineProviderService(); 31 | 32 | @Test(expected=REngineException.class) 33 | public void testThrowsEngineExceptionWhenSomethingGoesWrong() throws Exception { 34 | 35 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 36 | 37 | service.blockFunction(mock(REXPInteger.class), mock(REXPDouble.class)); 38 | } 39 | 40 | @Test 41 | public void testReturnResult() throws Exception { 42 | REXP expectedResult = mock(REXP.class); 43 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult); 44 | 45 | REXP result = service.blockFunction(mock(REXPInteger.class), mock(REXPDouble.class)); 46 | 47 | assertThat(result).isNotNull(); 48 | assertThat(result).isEqualTo(expectedResult); 49 | } 50 | 51 | @Test(expected=REngineException.class) 52 | public void testThrowsEngineExceptionWhenSomethingGoesWrongInBlockDiscrete() throws Exception { 53 | 54 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 55 | 56 | service.blockDiscreteFunction(mock(REXPInteger.class), mock(REXPString.class)); 57 | } 58 | 59 | @Test 60 | public void testReturnResultBlockDiscreteFunction() throws Exception { 61 | REXP expectedResult = mock(REXP.class); 62 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult); 63 | 64 | REXP result = service.blockDiscreteFunction(mock(REXPInteger.class), mock(REXPString.class)); 65 | 66 | assertThat(result).isNotNull(); 67 | assertThat(result).isEqualTo(expectedResult); 68 | } 69 | 70 | @Test(expected=REngineException.class) 71 | public void testThrowsEngineExceptionWhenSomethingGoesWrongCallingSqrt() throws Exception { 72 | 73 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 74 | 75 | service.sqrt(4d); 76 | } 77 | 78 | @Test 79 | public void testReturnResultWhenCallingSqrt() throws Exception { 80 | REXP expectedResult = new REXPDouble(2d); 81 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult); 82 | 83 | double result = service.sqrt(4d); 84 | 85 | assertThat(result).isEqualTo(2d); 86 | } 87 | 88 | @Test(expected=UnsupportedOperationException.class) 89 | public void ksTestIsNotImplementedYet() { 90 | 91 | service.ksTest(mock(REXPDouble.class), mock(REXPDouble.class)); 92 | } 93 | 94 | @Test(expected=UnsupportedOperationException.class) 95 | public void blockGeneralFunctionIsNotImplementedYet() { 96 | 97 | service.blockGeneralFunction(mock(REXPInteger.class), newList(), newList()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /blockFunction.r: -------------------------------------------------------------------------------- 1 | library(blockTools) 2 | library(plyr) 3 | 4 | # Block function for continuous variables 5 | blockFunction <- function(data,id.vars,block.vars) { 6 | 7 | # Check if the variance of all block.vars is zero. The block function doesn't work in that case 8 | allVarsAreZero = TRUE 9 | for(blockVar in block.vars) { 10 | if(var(data[[blockVar]]) != 0) allVarsAreZero = FALSE 11 | } 12 | 13 | # If the variance of all block.vars is not zero it's safe to call the block function 14 | if(!allVarsAreZero) { 15 | result <- block(data, 16 | n.tr = 2, # Match pairs, if 3 match triplets, and so on. 17 | id.vars = id.vars, 18 | block.vars = block.vars, 19 | algorithm="optGreedy", # Ok default 20 | distance = "mahalanobis", # OK default 21 | level.two = FALSE, 22 | verbose = FALSE) 23 | 24 | return(result$blocks[[1]]) 25 | } 26 | # The variance of all block.vars is zero. That means that all participants have the same value for 27 | # the block vars. We return a combination of pair randomly choosen among the original data 28 | else { 29 | result <- replicate(nrow(data)/2,sample(data[[id.vars]],2)) # Generate pairs 30 | result <- adply(result,2,.id=NULL) # Convert array to data.frame 31 | colnames(result) <- c("Unit 1","Unit 2") # Change column names to "Unit 1", "Unit2" 32 | 33 | return(result) 34 | } 35 | 36 | 37 | } 38 | 39 | # Adds a column to the data.frame with the name value. The value of that column will be 1 if the row has 40 | # that value for the column var and 0 otherwise. For example, if var = city and value = Madrid a new column 41 | # Madrid will be added with 1 if the row has city == Madrid or 0 otherwise. 42 | addColumnForVariable <- function(data,var,value) { 43 | 44 | data[data[[var]] %in% value,value] <- 1 45 | data[!(data[[var]] %in% value),value] <- 0 46 | 47 | 48 | return(data) 49 | 50 | } 51 | 52 | transformDiscreteVariables <- function(data,block.vars) { 53 | 54 | newBlockVars = NULL 55 | 56 | # We might have mutiple block.vars 57 | for(var in block.vars) { 58 | uniqueValues = as.character(unique(data[[var]])) # Get unique values for the blocking variable 59 | uniqueValues = uniqueValues[1:length(uniqueValues)-1] # Get all values except the last one. Block function doesn't work with all values 60 | 61 | newBlockVars = append(uniqueValues,newBlockVars) # Add the values to the blockVars vector 62 | 63 | for(value in uniqueValues) { # For every value, create a column with that value 64 | data <- addColumnForVariable(data,var,value) 65 | } 66 | } 67 | 68 | return(list(data=data,newBlockVars=newBlockVars)) 69 | 70 | } 71 | 72 | # Block function for discrete variables. It creates artificial variables for every value of the 73 | # discrete variables and use them as block variables in the block function 74 | blockDiscreteFunction <- function(data,id.vars,block.vars) { 75 | 76 | newBlockVars = NULL 77 | transform <- transformDiscreteVariables(data,block.vars) 78 | 79 | print(transform) 80 | 81 | result <- blockFunction(transform$data,id.vars,transform$newBlockVars) #Do the matching using the new columns 82 | 83 | return(result) 84 | } 85 | 86 | blockGeneralFunction <- function(data,id.vars,discreteVars,continuousVars) { 87 | 88 | transform <- transformDiscreteVariables(data,discreteVars) 89 | print(transform$data) 90 | result <- blockFunction(transform$data,id.vars,c(transform$newBlockVars,continuousVars)) 91 | 92 | return(result) 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/metrics/DatadogReporterConfig.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.metrics; 2 | 3 | import java.util.EnumSet; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import org.coursera.metrics.datadog.DatadogReporter; 7 | import org.coursera.metrics.datadog.DatadogReporter.Expansion; 8 | import org.coursera.metrics.datadog.transport.HttpTransport; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.context.properties.ConfigurationProperties; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Profile; 16 | 17 | import com.codahale.metrics.MetricRegistry; 18 | 19 | /** 20 | * This bean will create and configure a DatadogReporter that will be in charge of sending 21 | * all the metrics collected by Spring Boot actuator system to Datadog. 22 | * 23 | * @see https://www.datadoghq.com/ 24 | * @author jfcorugedo 25 | * 26 | */ 27 | @Configuration 28 | @ConfigurationProperties("rJavaServer.metrics") 29 | @Profile({ "!local"}) 30 | public class DatadogReporterConfig { 31 | 32 | private static final Logger LOGGER = LoggerFactory.getLogger(DatadogReporterConfig.class); 33 | 34 | /** Datadog API key used to authenticate every request to Datadog API */ 35 | private String apiKey; 36 | 37 | /** Logical name associated to all the events send by this application */ 38 | private String host; 39 | 40 | /** Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog */ 41 | private long period; 42 | 43 | @Bean 44 | @Autowired 45 | public DatadogReporter datadogReporter(MetricRegistry registry) { 46 | 47 | if(LOGGER.isInfoEnabled()) { 48 | LOGGER.info("Initializing Datadog reporter using [ host: {}, period(seconds):{}, api-key:{} ]", getHost(), getPeriod(), getApiKey()); 49 | } 50 | 51 | EnumSet expansions = DatadogReporter.Expansion.ALL; 52 | HttpTransport httpTransport = new HttpTransport 53 | .Builder() 54 | .withApiKey(getApiKey()) 55 | .build(); 56 | 57 | DatadogReporter reporter = DatadogReporter.forRegistry(registry) 58 | .withHost(getHost()) 59 | .withTransport(httpTransport) 60 | .withExpansions(expansions) 61 | .build(); 62 | 63 | reporter.start(getPeriod(), TimeUnit.SECONDS); 64 | 65 | if(LOGGER.isInfoEnabled()) { 66 | LOGGER.info("Datadog reporter successfully initialized"); 67 | } 68 | 69 | return reporter; 70 | } 71 | 72 | /** 73 | * @return Datadog API key used to authenticate every request to Datadog API 74 | */ 75 | public String getApiKey() { 76 | return apiKey; 77 | } 78 | 79 | /** 80 | * @param apiKey Datadog API key used to authenticate every request to Datadog API 81 | */ 82 | public void setApiKey(String apiKey) { 83 | this.apiKey = apiKey; 84 | } 85 | 86 | /** 87 | * @return Logical name associated to all the events send by this application 88 | */ 89 | public String getHost() { 90 | return host; 91 | } 92 | 93 | /** 94 | * @param host Logical name associated to all the events send by this application 95 | */ 96 | public void setHost(String host) { 97 | this.host = host; 98 | } 99 | 100 | /** 101 | * @return Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog 102 | */ 103 | public long getPeriod() { 104 | return period; 105 | } 106 | 107 | /** 108 | * @param period Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog 109 | */ 110 | public void setPeriod(long period) { 111 | this.period = period; 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RJavaServer [![Build Status](https://travis-ci.org/jfcorugedo/RJavaServer.svg?branch=master)](https://travis-ci.org/jfcorugedo/RJavaServer) [![Coverage Status](https://coveralls.io/repos/github/jfcorugedo/RJavaServer/badge.svg?branch=master)](https://coveralls.io/github/jfcorugedo/RJavaServer?branch=master) 2 | Java server that can call R functions using JRI and rJava packages 3 | 4 | ## Requirements 5 | 6 | This server needs an installation of R in the local OS, as well as the rJava package installed: 7 | 8 | * Install R in your machine (see https://cran.r-project.org/) 9 | * Install rJava using R console: type `install.package("rJava")` inside R console 10 | * Install Rserve using R console: type `install.package("Rserve")` inside R console 11 | 12 | Once these steps are done, you have to configure some variables inside your local system depending on the approach you're going to use. 13 | 14 | ### Rserve: 15 | 16 | * **R_HOME**: Pointing to your local R installation (Ej: /Library/Frameworks/R.framework/Resources) 17 | 18 | ### JRI: 19 | 20 | * **R_HOME**: Pointing to your local R installation (Ej: /Library/Frameworks/R.framework/Resources) 21 | * **LD_LIBRARY_PATH**: Pointing to R lib directory as well as JRI directory (EJ: /app/vendor/R/lib/R/lib:/app/vendor/R/lib/R/bin) 22 | 23 | To sum up, look at the content of the script $R_HOME/library/rJava/jri/run and configure your local system usign the same variable names and values. 24 | 25 | Moreover, the server must be started with the JVM parameter -D-Djava.library.path pointing at the JRI installation directory, ie: 26 | 27 | $JAVA_HOME/bin/java -Dserver.port=$PORT -Djava.library.path=/app/vendor/R/lib/R/library/rJava/jri/ -jar target/RJavaServer.jar 28 | 29 | If you have trouble finding this directory, try to look for the JRI SO library: 30 | 31 | find / -name "libjri.*" 32 | 33 | ## Heroku 34 | In order to execute this server in Heroku, a Procfile has been added to the project. It can be deployen to any Heroku applications that has R environment installed. 35 | 36 | To install R inside a heroku application, you have to use multipack-buildpack and r-buildpack. 37 | 38 | To install this project into a Heroku server, follow theses steps: 39 | 40 | 1. Create a new application that uses multipack buidpack typing in the command line: 41 | 42 | heroku create --stack cedar-14 --buildpack https://github.com/heroku/heroku-buildpack-multi.git --app RJavaServer 43 | 44 | 2. Create a file called `.buildpacks` inside your project, and type here all the Heroku buildpacks you want to install in your environment (**_this project already contains this file_**) 45 | 46 | 3. In addition to your application code, you must tell R which packages must be installed. This task is done inside a file called init.r (**_this project already contains a init.r script_**) 47 | 48 | 49 | ## Usage 50 | 51 | Right now, the REST API has only very basic operations, but you can extend it to call whatever R function you want. Moreover, you can create your own R script and load it using `engine.parseAndEval("source(\"/yourpath/yourscript.r\"")`. (See an example at UseREngineInFrontOfJRIEngineTest) 52 | 53 | To test the server, just try to execute a POST command over the `mean` resource. 54 | 55 | Curl example: 56 | 57 | `curl http://localhost:8080/sqrt -X POST -d "5" -H "Content-type: application/json" -i`` 58 | 59 | It should return: 60 | 61 | HTTP/1.1 201 Created 62 | Server: Apache-Coyote/1.1 63 | X-Application-Context: application:local:8080 64 | Content-Type: application/json;charset=UTF-8 65 | Transfer-Encoding: chunked 66 | Date: Sun, 20 Sep 2015 15:46:45 GMT 67 | 68 | 2.23606797749979 69 | 70 | 71 | ## Datadog 72 | 73 | This project has a module that automatically sends metrics to Datadog, an online monitoring system. 74 | 75 | To enable this feature, just create an account in Datadog (https://www.datadoghq.com/) and put your API Key in the configuration file (application.yml): 76 | 77 | metrics: 78 | apiKey: 79 | host: 80 | period: 81 | enabled: true 82 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/common/collection/CollectionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.common.collection; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.junit.Test; 11 | 12 | public class CollectionUtilsTest { 13 | 14 | @Test 15 | public void testCloneList() { 16 | List firstList = new ArrayList(); 17 | firstList.add(1); 18 | firstList.add("test"); 19 | firstList.add(3.5); 20 | 21 | List secondList = CollectionUtils.cloneList(firstList); 22 | 23 | firstList.add("another"); 24 | secondList.remove(1); 25 | 26 | assertThat(firstList).hasSize(4); 27 | assertThat(firstList).containsExactly(1,"test",3.5,"another"); 28 | assertThat(secondList).hasSize(2); 29 | assertThat(secondList).containsExactly(1,3.5); 30 | } 31 | 32 | 33 | @Test 34 | public void testConvertToPrimitives() { 35 | List listOfObjects = new ArrayList(); 36 | listOfObjects.add(Double.valueOf(1.0)); 37 | listOfObjects.add(Double.valueOf(2.0)); 38 | listOfObjects.add(Double.valueOf(3.0)); 39 | 40 | double[] arrayOfPrimitives = CollectionUtils.convertToPrimitives(listOfObjects); 41 | 42 | assertThat(arrayOfPrimitives).hasSize(3); 43 | assertThat(arrayOfPrimitives).containsExactly(1.0,2.0,3.0); 44 | } 45 | 46 | @Test 47 | public void testArrayToList() { 48 | Double[] array = new Double[]{1d,2d,3d,4d}; 49 | 50 | List list = CollectionUtils.arrayToList(array); 51 | 52 | assertThat(list).hasSize(4); 53 | assertThat(list).containsExactly(1d,2d,3d,4d); 54 | } 55 | 56 | @Test 57 | public void testIsEmptyMapReturnsTrueWhenNull() { 58 | 59 | Map nullMap = null; 60 | 61 | boolean result = CollectionUtils.isEmpty(nullMap); 62 | 63 | assertThat(result).isTrue(); 64 | assertThat(CollectionUtils.isNotEmpty(nullMap)).isFalse(); 65 | } 66 | 67 | @Test 68 | public void testIsEmptyMapReturnsTrueWhenThereIsNoElements() { 69 | 70 | Map emptyMap = new HashMap(); 71 | 72 | boolean result = CollectionUtils.isEmpty(emptyMap); 73 | 74 | assertThat(result).isTrue(); 75 | assertThat(CollectionUtils.isNotEmpty(emptyMap)).isFalse(); 76 | } 77 | 78 | @Test 79 | public void testIsEmptyMapReturnsFalseWhenThereIsAtLeastOneElements() { 80 | 81 | Map notEmptyMap = new HashMap(); 82 | notEmptyMap.put(new Object(), null); 83 | 84 | boolean result = CollectionUtils.isEmpty(notEmptyMap); 85 | 86 | assertThat(result).isFalse(); 87 | assertThat(CollectionUtils.isNotEmpty(notEmptyMap)).isTrue(); 88 | } 89 | 90 | @Test 91 | public void testIsEmptyListReturnsTrueWhenNull() { 92 | 93 | List nullList = null; 94 | 95 | boolean result = CollectionUtils.isEmpty(nullList); 96 | 97 | assertThat(result).isTrue(); 98 | assertThat(CollectionUtils.isNotEmpty(nullList)).isFalse(); 99 | } 100 | 101 | @Test 102 | public void testIsEmptyListReturnsTrueWhenThereIsNoElements() { 103 | 104 | List emptyList = new ArrayList(); 105 | 106 | boolean result = CollectionUtils.isEmpty(emptyList); 107 | 108 | assertThat(result).isTrue(); 109 | assertThat(CollectionUtils.isNotEmpty(emptyList)).isFalse(); 110 | } 111 | 112 | @Test 113 | public void testIsEmptyListReturnsFalseWhenThereIsAtLeastOneElements() { 114 | 115 | List notEmptyList = new ArrayList(); 116 | notEmptyList.add(null); 117 | 118 | boolean result = CollectionUtils.isEmpty(notEmptyList); 119 | 120 | assertThat(result).isFalse(); 121 | assertThat(CollectionUtils.isNotEmpty(notEmptyList)).isTrue(); 122 | } 123 | 124 | @Test 125 | public void testListToArray() { 126 | 127 | List list = CollectionUtils.newList(1d, 2d, 3d, 4d); 128 | 129 | double[] array = CollectionUtils.listToPrimitiveArray(list); 130 | 131 | assertThat(array).hasSize(list.size()); 132 | assertThat(array).containsExactly(1d,2d,3d,4d); 133 | } 134 | 135 | @Test 136 | public void testListToArrayReturnsEmptyArray() { 137 | 138 | List list = null; 139 | 140 | double[] array = CollectionUtils.listToPrimitiveArray(list); 141 | 142 | assertThat(array).hasSize(0); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/JRIEngineProviderServiceIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | 8 | import org.apache.commons.math3.distribution.EnumeratedDistribution; 9 | import org.apache.commons.math3.util.Pair; 10 | import org.junit.BeforeClass; 11 | import org.junit.Ignore; 12 | import org.junit.Test; 13 | import org.rosuda.REngine.REXP; 14 | import org.rosuda.REngine.REXPDouble; 15 | import org.rosuda.REngine.REXPGenericVector; 16 | import org.rosuda.REngine.REXPInteger; 17 | import org.rosuda.REngine.REXPString; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | @Ignore("These tests require R environment installed") 22 | public class JRIEngineProviderServiceIT { 23 | 24 | public static final Logger LOGGER = LoggerFactory.getLogger(JRIEngineProviderServiceIT.class); 25 | 26 | public static final double[] TEST_POPULATION = new double[20]; 27 | static{ 28 | TEST_POPULATION[0] = 10; 29 | TEST_POPULATION[1] = 5; 30 | TEST_POPULATION[2] = 20; 31 | TEST_POPULATION[3] = 12; 32 | TEST_POPULATION[4] = 4; 33 | TEST_POPULATION[5] = 11; 34 | TEST_POPULATION[6] = 23; 35 | TEST_POPULATION[7] = 0; 36 | TEST_POPULATION[8] = 12; 37 | TEST_POPULATION[9] = 6; 38 | TEST_POPULATION[10] = 6; 39 | TEST_POPULATION[11] = 11; 40 | TEST_POPULATION[12] = 14; 41 | TEST_POPULATION[13] = 13; 42 | TEST_POPULATION[14] = 16; 43 | TEST_POPULATION[15] = 18; 44 | TEST_POPULATION[16] = 19; 45 | TEST_POPULATION[17] = 12; 46 | TEST_POPULATION[18] = 0; 47 | TEST_POPULATION[19] = 4; 48 | } 49 | 50 | public static final double[] TEST_BIG_POPULATION = new double[200]; 51 | static{ 52 | for(int i = 0 ; i < 10 ; i++) { 53 | for(int j = 0 ; j < 20 ; j++) { 54 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j]; 55 | } 56 | } 57 | } 58 | 59 | private static JRIEngineProviderService providerService; 60 | 61 | @BeforeClass 62 | public static void setUp() { 63 | providerService = new JRIEngineProviderService(); 64 | providerService.setBlockFunction("source(\"/Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.R\")"); 65 | providerService.setUpR(); 66 | } 67 | 68 | @Test 69 | public void testBlockFunction() throws Exception{ 70 | 71 | for(int i = 0 ; i < 100 ; i++) { 72 | REXP result = providerService.blockFunction(new REXPInteger(generateIds(200)), new REXPDouble(TEST_BIG_POPULATION)); 73 | if(LOGGER.isInfoEnabled()) { 74 | LOGGER.info(blockResultToString(result)); 75 | } 76 | } 77 | } 78 | 79 | @Test 80 | @Ignore 81 | public void testBlockDiscreteFunction() { 82 | 83 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(200)), new REXPString(generateRandomCities(200))); 84 | 85 | if(LOGGER.isInfoEnabled()) { 86 | LOGGER.info(blockResultToString(result)); 87 | } 88 | } 89 | 90 | private String[] generateRandomCities(int sampleSize) { 91 | EnumeratedDistribution cityDistribution = 92 | new EnumeratedDistribution( 93 | newList( 94 | new Pair("Madrid", 1.0), 95 | new Pair("London", 1.0), 96 | new Pair("New York", 1.0), 97 | new Pair("Boston", 1.0), 98 | new Pair("Paris", 1.0), 99 | new Pair("Rome", 1.0), 100 | new Pair("Oslo", 1.0) 101 | )); 102 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]); 103 | } 104 | 105 | private int[] generateIds(int length) { 106 | int[] ids = new int[length]; 107 | for(int i = 0 ; i < length ; i++) { 108 | ids[i] = i; 109 | } 110 | return ids; 111 | } 112 | 113 | private String blockResultToString(REXP result) { 114 | REXPGenericVector vector = (REXPGenericVector) result; 115 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings(); 116 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings(); 117 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles(); 118 | 119 | StringBuilder sb = new StringBuilder("\n"); 120 | 121 | for(int i = 0 ; i < unit1.length ; i++) { 122 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n"); 123 | } 124 | 125 | return sb.toString(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/service/RServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.IntStream; 8 | 9 | import javax.inject.Inject; 10 | 11 | import org.rosuda.REngine.REXP; 12 | import org.rosuda.REngine.REXPDouble; 13 | import org.rosuda.REngine.REXPGenericVector; 14 | import org.rosuda.REngine.REXPInteger; 15 | import org.rosuda.REngine.REXPString; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.stereotype.Service; 19 | 20 | import com.jfcorugedo.rserver.engine.REngineProviderService; 21 | 22 | @Service 23 | public class RServiceImpl implements RService{ 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(RServiceImpl.class); 26 | 27 | @Inject 28 | private REngineProviderService engineProvider; 29 | 30 | @Override 31 | public List groupValues(List values) { 32 | 33 | REXPInteger rIds = new REXPInteger(generateIds(values.get(0).length)); 34 | REXPDouble[] rValues = values.stream().map(rawValues -> new REXPDouble(rawValues)).toArray(REXPDouble[]::new); 35 | 36 | REXP result = engineProvider.blockFunction(rIds, rValues); 37 | 38 | if(LOGGER.isDebugEnabled()) { 39 | LOGGER.debug("blockFunction:\n{}", blockResultToString(result)); 40 | } 41 | return formatResult(result); 42 | } 43 | 44 | @Override 45 | public List groupDiscreteValues(List values) { 46 | 47 | REXPInteger rIds = new REXPInteger(generateIds(values.get(0).length)); 48 | REXPString[] rValues = values.stream().map(rawValues -> new REXPString(rawValues)).toArray(REXPString[]::new); 49 | 50 | REXP result = engineProvider.blockDiscreteFunction(rIds, rValues); 51 | 52 | if(LOGGER.isDebugEnabled()) { 53 | LOGGER.debug("blockDiscreteFunction:\n{}", blockResultToString(result)); 54 | } 55 | return formatResult(result); 56 | } 57 | 58 | @Override 59 | public List groupMultipleValues(List discreteValues, List continuousValues) { 60 | 61 | REXPInteger rIds = new REXPInteger(generateIds(discreteValues.get(0).length)); 62 | List discreteNativeValues = discreteValues.stream() 63 | .map(values -> new REXPString(values)) 64 | .collect(Collectors.toList()); 65 | List continuousNativeValues = continuousValues.stream() 66 | .map(values -> new REXPDouble(values)) 67 | .collect(Collectors.toList()); 68 | 69 | REXP result = engineProvider.blockGeneralFunction(rIds, discreteNativeValues, continuousNativeValues); 70 | 71 | if(LOGGER.isDebugEnabled()) { 72 | LOGGER.debug("blockGeneralFunction:\n{}", blockResultToString(result)); 73 | } 74 | return formatResult(result); 75 | } 76 | 77 | /** 78 | * Format the results from Rserve objects to java standard objects. 79 | * If the results are odd, the last element of the list will contain only one number 80 | * @param result 81 | * @return 82 | */ 83 | private List formatResult(REXP result) { 84 | REXPGenericVector vector = (REXPGenericVector) result; 85 | 86 | Integer[] unit1 = getNumbers((REXP)vector.asList().get(0)); 87 | Integer[] unit2 = getNumbers((REXP)vector.asList().get(1)); 88 | 89 | List resultFormated = new ArrayList<>(unit1.length); 90 | for(int index = 0 ; index < unit2.length -1 ; index++) { 91 | resultFormated.add(new int[]{unit1[index], unit2[index]}); 92 | } 93 | if(unit2[unit2.length - 1] == null){ 94 | resultFormated.add(new int[]{unit1[unit2.length - 1]}); 95 | } else { 96 | resultFormated.add(new int[]{unit1[unit2.length - 1], unit2[unit2.length - 1]}); 97 | } 98 | 99 | return resultFormated; 100 | } 101 | 102 | private Integer[] getNumbers(REXP ids) { 103 | if(ids.isString()) { 104 | return Arrays.stream(((REXPString)ids).asStrings())//NOSONAR: This stream doesn't need to be closed 105 | .map( 106 | id -> { 107 | if(id != null) { 108 | return Integer.valueOf(id); 109 | } else { 110 | return null; 111 | } 112 | } 113 | ).toArray(Integer[]::new); 114 | } else { 115 | int[] idsAsArray = ((REXPInteger)ids).asIntegers(); 116 | return (Integer[])Arrays.stream(idsAsArray).boxed().toArray(Integer[]::new);//NOSONAR: This stream doesn't need to be closed 117 | } 118 | } 119 | 120 | private int[] generateIds(int length) { 121 | return IntStream.range(0, length).toArray();//NOSONAR: This stream doesn't need to be closed 122 | } 123 | 124 | private String blockResultToString(REXP result) { 125 | REXPGenericVector vector = (REXPGenericVector) result; 126 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings(); 127 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings(); 128 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles(); 129 | 130 | StringBuilder sb = new StringBuilder("\n"); 131 | 132 | for(int i = 0 ; i < unit1.length ; i++) { 133 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n"); 134 | } 135 | 136 | return sb.toString(); 137 | } 138 | 139 | @Override 140 | public double kolmogorovSmirnovTest(double[] x, double[] y) { 141 | 142 | REXPDouble sampleX = new REXPDouble(x); 143 | REXPDouble sampleY = new REXPDouble(y); 144 | 145 | return engineProvider.ksTest(sampleX, sampleY); 146 | } 147 | 148 | @Override 149 | public double sqrt(double number) { 150 | return engineProvider.sqrt(number); 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/JRIEngineProviderService.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import java.util.List; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import org.rosuda.JRI.Rengine; 8 | import org.rosuda.REngine.REXP; 9 | import org.rosuda.REngine.REXPDouble; 10 | import org.rosuda.REngine.REXPInteger; 11 | import org.rosuda.REngine.REXPString; 12 | import org.rosuda.REngine.REngine; 13 | import org.rosuda.REngine.RList; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 17 | import org.springframework.boot.context.properties.ConfigurationProperties; 18 | import org.springframework.stereotype.Service; 19 | 20 | @Service 21 | @ConfigurationProperties("rJavaServer.rengine") 22 | @ConditionalOnProperty(name="rJavaServer.rengine.enableJRI", havingValue="true") 23 | public class JRIEngineProviderService implements REngineProviderService { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(JRIEngineProviderService.class); 26 | 27 | private REngine engine; 28 | 29 | private String blockFunction; 30 | 31 | /** 32 | * This method initializes REngine properly and make all the operations needed 33 | * to set up the environment. 34 | * 35 | * This JRI implementation must load JRI library and starts JRIEngine 36 | * 37 | */ 38 | @PostConstruct 39 | public void setUpR() {//NOSONAR 40 | 41 | try { 42 | //make sure JRI lib can be loaded (it must be present in java.library.path parameter) 43 | //This line is necessary because Rengine.versionCheck() will execute a System.exit if 44 | //it can't load JRI library. 45 | System.loadLibrary("jri"); 46 | // just making sure we have the right version of everything 47 | if (!Rengine.versionCheck()) { 48 | LOGGER.error("** Version mismatch - Java files don't match library version."); 49 | LOGGER.error(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion())); 50 | } 51 | 52 | // Enables debug traces 53 | Rengine.DEBUG = 1; 54 | 55 | if(LOGGER.isInfoEnabled()) { 56 | LOGGER.info("Creating Rengine (with arguments)"); 57 | } 58 | // 1) we pass the arguments from the command line 59 | // 2) we won't use the main loop at first, we'll start it later 60 | // (that's the "false" as second argument) 61 | // 3) no callback class will be used 62 | this.engine = REngine.engineForClass("org.rosuda.REngine.JRI.JRIEngine", new String[] { "--no-save" }, new REngineStdOutCallback(LOGGER), false); 63 | if(LOGGER.isInfoEnabled()) { 64 | LOGGER.info("Rengine created..."); 65 | LOGGER.info("Loading blockFunction from " + getBlockFunction()); 66 | } 67 | 68 | REXP result = engine.parseAndEval(getBlockFunction()); 69 | if(result == null) { 70 | LOGGER.error("blockFunction is not loaded!"); 71 | } else if(LOGGER.isInfoEnabled()) { 72 | LOGGER.info("blockFunction loaded successfully"); 73 | } 74 | } catch(Exception|UnsatisfiedLinkError e) { 75 | LOGGER.error("Unexpected error setting up R", e); 76 | } 77 | } 78 | 79 | @Override 80 | public REXP blockDiscreteFunction(REXPInteger ids, REXPString... values) { 81 | RList data = new RList(); 82 | data.add(ids); 83 | data.setKeyAt(0, "ids"); 84 | StringBuilder valueNames = new StringBuilder(); 85 | for(int i = 0 ; i < values.length ; i++) { 86 | data.add(values[i]); 87 | valueNames.append("\"values"+i+"\","); 88 | data.setKeyAt(i+1, "values"+i); 89 | } 90 | 91 | String variableNames = valueNames.substring(0, valueNames.length()-1); 92 | try{ 93 | synchronized(engine){ 94 | engine.assign("data", REXP.createDataFrame(data)); 95 | return engine.parseAndEval("blockDiscreteFunction(data,c(\"ids\"),c(" + variableNames + "))"); 96 | } 97 | }catch(Exception e) { 98 | throw new REngineException("Unexpected error while executing blockDiscreteFunction", e); 99 | } 100 | } 101 | 102 | @Override 103 | public double sqrt(double number) { 104 | try { 105 | synchronized(engine) { 106 | return engine.parseAndEval(String.format("sqrt(%f)", number)).asDouble(); 107 | } 108 | } catch(Exception e) { 109 | throw new REngineException("Unexpected error while executing sqrt function", e); 110 | } 111 | } 112 | 113 | public String getBlockFunction() { 114 | return blockFunction; 115 | } 116 | 117 | public void setBlockFunction(String blockFunction) { 118 | this.blockFunction = blockFunction; 119 | } 120 | 121 | @Override 122 | public REXP blockFunction(REXPInteger ids, REXPDouble... values) { 123 | 124 | RList data = new RList(); 125 | data.add(ids); 126 | data.setKeyAt(0, "ids"); 127 | StringBuilder valueNames = new StringBuilder(); 128 | for(int i = 0 ; i < values.length ; i++) { 129 | data.add(values[i]); 130 | valueNames.append("\"values"+i+"\","); 131 | data.setKeyAt(i+1, "values"+i); 132 | } 133 | 134 | try{ 135 | synchronized(engine){ 136 | engine.assign("data", REXP.createDataFrame(data)); 137 | return engine.parseAndEval("blockFunction(data,c(\"ids\"),c("+ valueNames.substring(0, valueNames.length()-1) +"))"); 138 | } 139 | }catch(Exception e) { 140 | throw new REngineException("Unexpected error while executing blockFunction", e); 141 | } 142 | } 143 | 144 | @Override 145 | public REXP blockGeneralFunction(REXPInteger ids, List discreteValues, 146 | List continuousValues) { 147 | throw new UnsupportedOperationException("JRI engine doesn't implement this method yet"); 148 | } 149 | 150 | @Override 151 | public double ksTest(REXPDouble x, REXPDouble y) { 152 | 153 | throw new UnsupportedOperationException("JRI engine doesn't implement this method yet"); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/RServeEngineProviderServiceIT.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import javax.inject.Inject; 12 | 13 | import org.apache.commons.math3.distribution.EnumeratedDistribution; 14 | import org.apache.commons.math3.util.Pair; 15 | import org.junit.Ignore; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | import org.rosuda.REngine.REXP; 19 | import org.rosuda.REngine.REXPDouble; 20 | import org.rosuda.REngine.REXPGenericVector; 21 | import org.rosuda.REngine.REXPInteger; 22 | import org.rosuda.REngine.REXPString; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | import org.springframework.boot.test.SpringApplicationConfiguration; 26 | import org.springframework.boot.test.WebIntegrationTest; 27 | import org.springframework.test.context.ActiveProfiles; 28 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 29 | 30 | import com.jfcorugedo.rserver.Application; 31 | 32 | /** 33 | * In order to make these test work R_HOME environment variable must be present 34 | * 35 | * Export an environment variable with this value: 36 | * 37 | * R_HOME=/Library/Frameworks/R.framework/Resources 38 | * 39 | * @author jfcorugedo 40 | * 41 | */ 42 | @Ignore("These tests require R environment installed and R_HOME variable setted") 43 | @RunWith(SpringJUnit4ClassRunner.class) 44 | @SpringApplicationConfiguration(classes = Application.class) 45 | @WebIntegrationTest 46 | @ActiveProfiles(profiles={"local", "integrationtest"}) 47 | public class RServeEngineProviderServiceIT { 48 | 49 | public static final Logger LOGGER = LoggerFactory.getLogger(RServeEngineProviderServiceIT.class); 50 | 51 | public static final double[] TEST_POPULATION = new double[20]; 52 | static{ 53 | TEST_POPULATION[0] = 10; 54 | TEST_POPULATION[1] = 5; 55 | TEST_POPULATION[2] = 20; 56 | TEST_POPULATION[3] = 12; 57 | TEST_POPULATION[4] = 4; 58 | TEST_POPULATION[5] = 11; 59 | TEST_POPULATION[6] = 23; 60 | TEST_POPULATION[7] = 0; 61 | TEST_POPULATION[8] = 12; 62 | TEST_POPULATION[9] = 6; 63 | TEST_POPULATION[10] = 6; 64 | TEST_POPULATION[11] = 11; 65 | TEST_POPULATION[12] = 14; 66 | TEST_POPULATION[13] = 13; 67 | TEST_POPULATION[14] = 16; 68 | TEST_POPULATION[15] = 18; 69 | TEST_POPULATION[16] = 19; 70 | TEST_POPULATION[17] = 12; 71 | TEST_POPULATION[18] = 0; 72 | TEST_POPULATION[19] = 4; 73 | } 74 | 75 | public static final double[] TEST_BIG_POPULATION = new double[200]; 76 | static{ 77 | for(int i = 0 ; i < 10 ; i++) { 78 | for(int j = 0 ; j < 20 ; j++) { 79 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j]; 80 | } 81 | } 82 | } 83 | 84 | 85 | @Inject 86 | private RServeEngineProviderService providerService; 87 | 88 | @Test 89 | public void testBlockFunction() throws Exception{ 90 | 91 | List bigPopulation = Arrays.stream(TEST_BIG_POPULATION).boxed().collect(Collectors.toList()); 92 | Collections.shuffle(bigPopulation); 93 | double[] shuffledPopulation = bigPopulation.stream().mapToDouble(Double::doubleValue).toArray(); 94 | REXP result = providerService.blockFunction(new REXPInteger(generateIds(200)), new REXPDouble(TEST_BIG_POPULATION), new REXPDouble(shuffledPopulation)); 95 | 96 | if(LOGGER.isInfoEnabled()) { 97 | LOGGER.info(blockResultToString(result)); 98 | } 99 | 100 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(100); 101 | } 102 | 103 | @Test 104 | public void testGenericBlockFunction() { 105 | 106 | List bigPopulation = Arrays.stream(TEST_BIG_POPULATION).boxed().collect(Collectors.toList()); 107 | Collections.shuffle(bigPopulation); 108 | double[] shuffledPopulation = bigPopulation.stream().mapToDouble(Double::doubleValue).toArray(); 109 | REXP result = providerService.blockGeneralFunction(new REXPInteger(generateIds(200)), newList(new REXPString(generateRandomCities(200))), newList(new REXPDouble(shuffledPopulation))); 110 | 111 | if(LOGGER.isInfoEnabled()) { 112 | LOGGER.info(blockResultToString(result)); 113 | } 114 | 115 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(100); 116 | } 117 | 118 | @Test 119 | public void testBlockDiscreteFunction() { 120 | 121 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(50)), new REXPString(generateRandomCities(50))); 122 | 123 | if(LOGGER.isInfoEnabled()) { 124 | LOGGER.info(blockResultToString(result)); 125 | } 126 | 127 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(25); 128 | } 129 | 130 | @Test 131 | public void testBlockDiscreteFunctionUsingTwoVariables() { 132 | 133 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(50)), new REXPString(generateRandomCities(50)), new REXPString(generateRandomCountries(50))); 134 | 135 | if(LOGGER.isInfoEnabled()) { 136 | LOGGER.info(blockResultToString(result)); 137 | } 138 | 139 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(25); 140 | } 141 | 142 | private String[] generateRandomCities(int sampleSize) { 143 | EnumeratedDistribution cityDistribution = 144 | new EnumeratedDistribution( 145 | newList( 146 | new Pair("Madrid", 1.0), 147 | new Pair("London", 1.0), 148 | new Pair("New York", 1.0), 149 | new Pair("Boston", 1.0), 150 | new Pair("Paris", 1.0), 151 | new Pair("Rome", 1.0), 152 | new Pair("Oslo", 1.0) 153 | )); 154 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]); 155 | } 156 | 157 | private String[] generateRandomCountries(int sampleSize) { 158 | EnumeratedDistribution cityDistribution = 159 | new EnumeratedDistribution( 160 | newList( 161 | new Pair("Spain", 1.0), 162 | new Pair("England", 1.0), 163 | new Pair("USA", 1.0), 164 | new Pair("France", 1.0), 165 | new Pair("Italy", 1.0), 166 | new Pair("Norway", 1.0), 167 | new Pair("Germany", 1.0) 168 | )); 169 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]); 170 | } 171 | 172 | private int[] generateIds(int length) { 173 | int[] ids = new int[length]; 174 | for(int i = 0 ; i < length ; i++) { 175 | ids[i] = i; 176 | } 177 | return ids; 178 | } 179 | 180 | private String blockResultToString(REXP result) { 181 | REXPGenericVector vector = (REXPGenericVector) result; 182 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings(); 183 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings(); 184 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles(); 185 | 186 | StringBuilder sb = new StringBuilder("\n"); 187 | 188 | for(int i = 0 ; i < unit1.length ; i++) { 189 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n"); 190 | } 191 | 192 | return sb.toString(); 193 | } 194 | 195 | @Test 196 | public void ksTest() { 197 | 198 | REXPDouble x = new REXPDouble(new double[]{3.5,4.0,5.0,25.0}); 199 | REXPDouble y = new REXPDouble(new double[]{3.5,4.0,5.0,25.0}); 200 | 201 | double pValue = providerService.ksTest(x, y); 202 | 203 | assertThat(pValue).isGreaterThan(0.05); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/com/jfcorugedo/rserver/engine/RServeEngineProviderService.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static java.lang.String.format; 4 | 5 | import java.util.List; 6 | 7 | import javax.annotation.PostConstruct; 8 | import javax.annotation.PreDestroy; 9 | import javax.inject.Inject; 10 | 11 | import org.rosuda.REngine.REXP; 12 | import org.rosuda.REngine.REXPDouble; 13 | import org.rosuda.REngine.REXPInteger; 14 | import org.rosuda.REngine.REXPString; 15 | import org.rosuda.REngine.REngine; 16 | import org.rosuda.REngine.RList; 17 | import org.rosuda.REngine.Rserve.RConnection; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 21 | import org.springframework.boot.context.properties.ConfigurationProperties; 22 | import org.springframework.stereotype.Service; 23 | 24 | @Service 25 | @ConfigurationProperties("rJavaServer.rengine") 26 | @ConditionalOnProperty(name="rJavaServer.rengine.enableJRI", havingValue="false", matchIfMissing=true) 27 | public class RServeEngineProviderService implements REngineProviderService { 28 | 29 | private static final Logger LOGGER = LoggerFactory.getLogger(RServeEngineProviderService.class); 30 | 31 | /** Code returned by R script when everything goes ok */ 32 | private static final int SUCCESS_CODE = 0; 33 | 34 | /** Full path to the rserve configuration file */ 35 | private String rserveConf; 36 | 37 | /** Full path to the R executable file */ 38 | private String rexe; 39 | 40 | @Inject 41 | private RConnectionFactory rConnectionFactory; 42 | 43 | /** 44 | * This method initializes REngine properly and make all the operations needed 45 | * to set up the environment. 46 | * 47 | * This RServe implementation must run R in a separate process and check the connection. 48 | * 49 | */ 50 | @PostConstruct 51 | public void setUpR() {//NOSONAR 52 | 53 | REngine engine = null; 54 | try { 55 | 56 | shutdownPreviousRuningInstances(); 57 | 58 | String command = String.format("echo 'library(Rserve);Rserve(FALSE,args=\"--no-save --slave --RS-conf %s\")'|%s --no-save --slave", rserveConf, rexe); 59 | 60 | if(LOGGER.isInfoEnabled()) { 61 | LOGGER.info("Starting RServe process using this command:\n{}", command); 62 | } 63 | ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", command); 64 | builder.inheritIO(); 65 | Process rProcess = builder.start(); 66 | 67 | if(LOGGER.isInfoEnabled()) { 68 | LOGGER.info("Waiting for Rserve to start..."); 69 | } 70 | int execCodeResult = rProcess.waitFor(); 71 | 72 | if(execCodeResult != SUCCESS_CODE) { 73 | LOGGER.error(String.format("Unexpected error code starting RServe: %d", execCodeResult)); 74 | } else { 75 | LOGGER.error("RServe started successfully"); 76 | } 77 | 78 | if(LOGGER.isInfoEnabled()) { 79 | LOGGER.info("Opening connection to RServe daemon...."); 80 | } 81 | engine = rConnectionFactory.getConnection(); 82 | if(LOGGER.isInfoEnabled()) { 83 | LOGGER.info(String.format("Obtaining R server version: %d", ((RConnection)engine).getServerVersion())); 84 | } 85 | 86 | } catch(Exception e) { 87 | LOGGER.error("Unexpected error setting up RServe environment", e); 88 | } finally { 89 | rConnectionFactory.releaseConnection(engine); 90 | } 91 | } 92 | 93 | protected void shutdownPreviousRuningInstances() { 94 | if(LOGGER.isInfoEnabled()) { 95 | LOGGER.info("Cleaning old Rserve running instances..."); 96 | } 97 | 98 | tearDown(); 99 | } 100 | 101 | @PreDestroy 102 | public void tearDown() { 103 | try { 104 | if(LOGGER.isInfoEnabled()) { 105 | LOGGER.info("Shuting down Rserve daemon...."); 106 | } 107 | RConnection rConnection = rConnectionFactory.getConnection(); 108 | rConnection.shutdown(); 109 | rConnectionFactory.releaseConnection(rConnection); 110 | if(LOGGER.isInfoEnabled()) { 111 | LOGGER.info("Shutdown signal sent to Rserve daemon"); 112 | } 113 | } catch(Exception e) { 114 | LOGGER.error("Unexpected error shuting down RServe", e); 115 | } 116 | } 117 | 118 | @Override 119 | public REXP blockFunction(REXPInteger ids, REXPDouble... values) { 120 | 121 | RList data = new RList(); 122 | data.add(ids); 123 | data.setKeyAt(0, "ids"); 124 | StringBuilder valueNames = new StringBuilder(); 125 | for(int i = 0 ; i < values.length ; i++) { 126 | data.add(values[i]); 127 | valueNames.append(format("\"values%d\",", i)); 128 | data.setKeyAt(i+1, format("values%d",i)); 129 | } 130 | 131 | String variableNames = valueNames.substring(0, valueNames.length()-1); 132 | REngine engine = null; 133 | try { 134 | engine = rConnectionFactory.getConnection(); 135 | engine.assign("data", REXP.createDataFrame(data)); 136 | return engine.parseAndEval(format("blockFunction(data,c(\"ids\"),c(%s))", variableNames)); 137 | }catch(Exception e) { 138 | throw new REngineException("Unexpected error while executing blockFunction", e); 139 | } finally { 140 | rConnectionFactory.releaseConnection(engine); 141 | } 142 | } 143 | 144 | @Override 145 | public REXP blockGeneralFunction(REXPInteger ids, List discreteValues, List continuousValues) { 146 | 147 | RList data = new RList(); 148 | data.add(ids); 149 | data.setKeyAt(0, "ids"); 150 | StringBuilder discreteVariables = new StringBuilder(); 151 | for(int i = 0 ; i < discreteValues.size() ; i++) { 152 | data.add(discreteValues.get(i)); 153 | discreteVariables.append(format("\"discreteValues%d\",", i)); 154 | data.setKeyAt(i+1, format("discreteValues%d", i)); 155 | } 156 | 157 | StringBuilder continuousVariables = new StringBuilder(); 158 | for(int i = 0 ; i < continuousValues.size() ; i++) { 159 | data.add(continuousValues.get(i)); 160 | continuousVariables.append(format("\"continuousValues%d\",", i)); 161 | data.setKeyAt(i + discreteValues.size() + 1, format("continuousValues%d", i)); 162 | } 163 | 164 | String discreteVariableNames = discreteVariables.substring(0, discreteVariables.length()-1); 165 | String continuousVariableNames = continuousVariables.substring(0, continuousVariables.length()-1); 166 | REngine engine = null; 167 | try { 168 | engine = rConnectionFactory.getConnection(); 169 | engine.assign("data", REXP.createDataFrame(data)); 170 | return engine.parseAndEval(format("blockGeneralFunction(data,c(\"ids\"),c(%s),c(%s))", discreteVariableNames, continuousVariableNames)); 171 | }catch(Exception e) { 172 | throw new REngineException("Unexpected error while executing blockFunction", e); 173 | } finally { 174 | rConnectionFactory.releaseConnection(engine); 175 | } 176 | } 177 | 178 | @Override 179 | public REXP blockDiscreteFunction(REXPInteger ids, REXPString... values) { 180 | RList data = new RList(); 181 | data.add(ids); 182 | data.setKeyAt(0, "ids"); 183 | StringBuilder valueNames = new StringBuilder(); 184 | for(int i = 0 ; i < values.length ; i++) { 185 | data.add(values[i]); 186 | valueNames.append(format("\"values%d\",", i)); 187 | data.setKeyAt(i+1, format("values%d",i)); 188 | } 189 | 190 | REngine engine = null; 191 | String variableNames = valueNames.substring(0, valueNames.length()-1); 192 | try { 193 | engine = rConnectionFactory.getConnection(); 194 | engine.assign("data", REXP.createDataFrame(data)); 195 | return engine.parseAndEval(format("blockDiscreteFunction(data,c(\"ids\"),c(%s))", variableNames)); 196 | }catch(Exception e) { 197 | throw new REngineException("Unexpected error while executing blockDiscreteFunction", e); 198 | } finally { 199 | rConnectionFactory.releaseConnection(engine); 200 | } 201 | } 202 | 203 | @Override 204 | public double sqrt(double number) { 205 | REngine engine = null; 206 | try { 207 | engine = rConnectionFactory.getConnection(); 208 | return engine.parseAndEval(String.format("sqrt(%f)", number)).asDouble(); 209 | } catch(Exception e) { 210 | throw new REngineException("Unexpected error while executing sqrt function", e); 211 | } finally { 212 | rConnectionFactory.releaseConnection(engine); 213 | } 214 | } 215 | 216 | public String getRserveConf() { 217 | return rserveConf; 218 | } 219 | 220 | public void setRserveConf(String rserveConf) { 221 | this.rserveConf = rserveConf; 222 | } 223 | 224 | public String getRexe() { 225 | return rexe; 226 | } 227 | 228 | public void setRexe(String rexe) { 229 | this.rexe = rexe; 230 | } 231 | 232 | @Override 233 | public double ksTest(REXPDouble x, REXPDouble y) { 234 | 235 | REngine engine = null; 236 | try { 237 | engine = rConnectionFactory.getConnection(); 238 | engine.assign("x", x); 239 | engine.assign("y", y); 240 | return engine.parseAndEval("ks.test(x, y)['p.value']").asList().at(0).asDouble(); 241 | }catch(Exception e) { 242 | throw new REngineException("Unexpected error while executing Kolmogorov-Smirnov test", e); 243 | } finally { 244 | rConnectionFactory.releaseConnection(engine); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.jfcorugedo.rserver 5 | rjavaserver 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-parent 11 | 1.2.7.RELEASE 12 | 13 | 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 1.8 19 | com.jfcorugedo.rserver.Application 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-actuator 30 | 31 | 32 | org.rosuda.REngine 33 | REngine 34 | 2.1.0 35 | 36 | 37 | com.github.lucarosellini.rJava 38 | JRI 39 | 0.9-7 40 | 41 | 42 | com.github.lucarosellini.rJava 43 | JRIEngine 44 | 0.9-7 45 | 46 | 47 | org.rosuda.REngine 48 | Rserve 49 | 1.8.1 50 | 51 | 52 | javax.inject 53 | javax.inject 54 | 1 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-aop 61 | 62 | 63 | io.dropwizard.metrics 64 | metrics-core 65 | 66 | 67 | com.ryantenney.metrics 68 | metrics-spring 69 | 3.1.2 70 | 71 | 72 | 73 | 74 | 75 | org.coursera 76 | dropwizard-metrics-datadog 77 | 1.1.3 78 | 79 | 80 | org.eclipse.jetty 81 | jetty-server 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-test 91 | test 92 | 93 | 94 | org.assertj 95 | assertj-core 96 | 3.0.0 97 | test 98 | 99 | 100 | junit 101 | junit 102 | test 103 | 104 | 105 | org.mockito 106 | mockito-core 107 | test 108 | 109 | 110 | org.apache.commons 111 | commons-math3 112 | 3.5 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-configuration-processor 117 | true 118 | 119 | 120 | org.apache.commons 121 | commons-lang3 122 | 3.3.2 123 | 124 | 125 | 126 | 127 | rjavaserver 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-maven-plugin 132 | 133 | 134 | maven-failsafe-plugin 135 | 136 | 137 | 138 | integration-test 139 | verify 140 | 141 | 142 | true 143 | 144 | **/*IT.java 145 | 146 | 147 | **/Application.java 148 | 149 | ${jacoco.it.argLine} 150 | UTF-8 151 | alphabetical 152 | 153 | 154 | 155 | 156 | 157 | maven-surefire-plugin 158 | 159 | true 160 | 161 | **/*Test.java 162 | 163 | 164 | **/*IT.java 165 | **/Application.java 166 | 167 | 168 | ${jacoco.argLine} 169 | 170 | 171 | alphabetical 172 | 173 | 174 | 175 | org.apache.maven.surefire 176 | surefire-junit47 177 | 2.15 178 | 179 | 180 | 181 | 182 | org.apache.maven.plugins 183 | maven-enforcer-plugin 184 | 1.3.1 185 | 186 | 187 | enforce-versions 188 | 189 | enforce 190 | 191 | 192 | 193 | 194 | 195 | 196 | You are running an older version of Maven. This 197 | application requires at least Maven 3.0 198 | [3.0.0,) 199 | 200 | 201 | You are running an older version of Java. This 202 | application requires at least JDK ${java.version} 203 | [${java.version},) 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | org.jacoco 212 | jacoco-maven-plugin 213 | 0.7.6.201602180812 214 | 215 | 216 | prepare-agent 217 | 218 | prepare-agent 219 | 220 | 221 | 222 | **/*Test 223 | 224 | 225 | com.jfcorugedo.* 226 | 227 | jacoco.argLine 228 | 229 | 230 | 231 | 232 | report 233 | prepare-package 234 | 235 | report 236 | 237 | 238 | 239 | 240 | prepare-agent-it 241 | pre-integration-test 242 | 243 | prepare-agent 244 | 245 | 246 | ${project.build.directory}/jacoco-integration.exec 247 | 248 | **/*IT 249 | 250 | 251 | com.jfcorugedo.* 252 | 253 | jacoco.it.argLine 254 | 255 | 256 | 257 | 258 | report-it 259 | post-integration-test 260 | 261 | report 262 | 263 | 264 | ${project.reporting.outputDirectory}/jacoco-it 265 | ${project.build.directory}/jacoco-integration.exec 266 | 267 | 268 | 269 | 270 | 271 | 272 | org.eluder.coveralls 273 | coveralls-maven-plugin 274 | 4.1.0 275 | 276 | 277 | 278 | 279 | 280 | org.sonarsource.scanner.maven 281 | sonar-maven-plugin 282 | 3.0.1 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | sonar 291 | 292 | true 293 | 294 | 295 | 296 | http://my-sonar.herokuapp.com/ 297 | admin 298 | admin.my-sonar 299 | java 300 | true 301 | target/jacoco-integration.exec 302 | 1.8 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/engine/RServeEngineProviderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.engine; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.mockito.Matchers.any; 6 | import static org.mockito.Matchers.anyString; 7 | import static org.mockito.Matchers.eq; 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.Mockito.when; 12 | 13 | import java.util.List; 14 | 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | import org.mockito.ArgumentCaptor; 18 | import org.mockito.InjectMocks; 19 | import org.mockito.Mock; 20 | import org.mockito.junit.MockitoJUnit; 21 | import org.mockito.junit.MockitoRule; 22 | import org.rosuda.REngine.REXP; 23 | import org.rosuda.REngine.REXPDouble; 24 | import org.rosuda.REngine.REXPGenericVector; 25 | import org.rosuda.REngine.REXPInteger; 26 | import org.rosuda.REngine.REXPString; 27 | import org.rosuda.REngine.RList; 28 | import org.rosuda.REngine.Rserve.RConnection; 29 | 30 | public class RServeEngineProviderServiceTest { 31 | 32 | @Rule 33 | public MockitoRule rule = MockitoJUnit.rule(); 34 | 35 | @Mock 36 | private RConnectionFactory rConnectionFactory; 37 | 38 | @InjectMocks 39 | private RServeEngineProviderService rServeEngineProviderService; 40 | 41 | @Test 42 | public void blockGeneralFunction() throws Exception{ 43 | RConnection rConnectionMock = mock(RConnection.class); 44 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 45 | 46 | rServeEngineProviderService.blockGeneralFunction(getDemoIds(), getDemoDiscreteVars(), getDemoContinuousVars()); 47 | 48 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 49 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 50 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 51 | assertThat(rCommand.getValue()).isEqualTo("blockGeneralFunction(data,c(\"ids\"),c(\"discreteValues0\"),c(\"continuousValues0\"))"); 52 | } 53 | 54 | private List getDemoDiscreteVars() { 55 | 56 | return newList(new REXPString(new String[]{"A", "B", "B", "A"})); 57 | } 58 | 59 | private List getDemoContinuousVars() { 60 | 61 | return newList(new REXPDouble(new double[]{1.0, 2.0, 3.0, 4.0})); 62 | } 63 | 64 | private REXPInteger getDemoIds() { 65 | 66 | return new REXPInteger(new int[]{0,1,2,3}); 67 | } 68 | 69 | @Test(expected=REngineException.class) 70 | public void blockGeneralFunctionUnexpectedError() throws Exception{ 71 | 72 | RConnection rConnectionMock = mock(RConnection.class); 73 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 74 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 75 | 76 | rServeEngineProviderService.blockGeneralFunction(getDemoIds(), getDemoDiscreteVars(), getDemoContinuousVars()); 77 | 78 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 79 | verify(rConnectionMock, times(1)).parseAndEval(anyString()); 80 | } 81 | 82 | @Test 83 | public void blockFunction() throws Exception{ 84 | RConnection rConnectionMock = mock(RConnection.class); 85 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 86 | 87 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0)); 88 | 89 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 90 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 91 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 92 | assertThat(rCommand.getValue()).isEqualTo("blockFunction(data,c(\"ids\"),c(\"values0\"))"); 93 | } 94 | 95 | @Test 96 | public void blockFunctionUsingMoreThanOneVariable() throws Exception{ 97 | RConnection rConnectionMock = mock(RConnection.class); 98 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 99 | 100 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0), getDemoContinuousVars().get(0)); 101 | 102 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 103 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 104 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 105 | assertThat(rCommand.getValue()).isEqualTo("blockFunction(data,c(\"ids\"),c(\"values0\",\"values1\"))"); 106 | } 107 | 108 | @Test(expected=REngineException.class) 109 | public void blockFunctionUnexpectedError() throws Exception{ 110 | 111 | RConnection rConnectionMock = mock(RConnection.class); 112 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 113 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 114 | 115 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0)); 116 | 117 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 118 | verify(rConnectionMock, times(1)).parseAndEval(anyString()); 119 | } 120 | 121 | @Test 122 | public void blockDiscreteFunction() throws Exception{ 123 | RConnection rConnectionMock = mock(RConnection.class); 124 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 125 | 126 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0)); 127 | 128 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 129 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 130 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 131 | assertThat(rCommand.getValue()).isEqualTo("blockDiscreteFunction(data,c(\"ids\"),c(\"values0\"))"); 132 | } 133 | 134 | @Test 135 | public void blockDiscreteFunctionUsingMoreThanOneVariable() throws Exception{ 136 | RConnection rConnectionMock = mock(RConnection.class); 137 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 138 | 139 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0), getDemoDiscreteVars().get(0)); 140 | 141 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 142 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 143 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 144 | assertThat(rCommand.getValue()).isEqualTo("blockDiscreteFunction(data,c(\"ids\"),c(\"values0\",\"values1\"))"); 145 | } 146 | 147 | @Test(expected=REngineException.class) 148 | public void blockDiscreteFunctionUnexpectedError() throws Exception{ 149 | 150 | RConnection rConnectionMock = mock(RConnection.class); 151 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong...")); 152 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 153 | 154 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0)); 155 | 156 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class)); 157 | verify(rConnectionMock, times(1)).parseAndEval(anyString()); 158 | } 159 | 160 | @Test 161 | public void ksTest() throws Exception{ 162 | 163 | RConnection rConnectionMock = mock(RConnection.class); 164 | when(rConnectionMock.parseAndEval(anyString())).thenReturn(new REXPGenericVector(new RList(new REXP[]{new REXPDouble(new double[]{0.15})}))); 165 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 166 | REXPDouble demoVars = getDemoContinuousVars().get(0); 167 | 168 | rServeEngineProviderService.ksTest(demoVars, demoVars); 169 | 170 | verify(rConnectionMock, times(1)).assign(eq("x"), any(REXP.class)); 171 | verify(rConnectionMock, times(1)).assign(eq("y"), any(REXP.class)); 172 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class); 173 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture()); 174 | assertThat(rCommand.getValue()).isEqualTo("ks.test(x, y)['p.value']"); 175 | } 176 | 177 | @Test(expected=REngineException.class) 178 | public void ksTestThrowsException() throws Exception{ 179 | 180 | RConnection rConnectionMock = mock(RConnection.class); 181 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException()); 182 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 183 | REXPDouble demoVars = getDemoContinuousVars().get(0); 184 | 185 | rServeEngineProviderService.ksTest(demoVars, demoVars); 186 | } 187 | 188 | @Test 189 | public void testSqrt() throws Exception{ 190 | 191 | RConnection rConnectionMock = mock(RConnection.class); 192 | when(rConnectionMock.parseAndEval("sqrt(4.000000)")).thenReturn(new REXPDouble(2.0)); 193 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 194 | 195 | double result = rServeEngineProviderService.sqrt(4.0); 196 | 197 | assertThat(result).isEqualTo(2.0); 198 | } 199 | 200 | @Test(expected=REngineException.class) 201 | public void testSqrtThrowsException() throws Exception{ 202 | 203 | RConnection rConnectionMock = mock(RConnection.class); 204 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException()); 205 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 206 | 207 | rServeEngineProviderService.sqrt(4.0); 208 | } 209 | 210 | @Test 211 | public void shutdownPreviousRuningInstancesTriesToShutdownAndCloseConnection() throws Exception{ 212 | 213 | RConnection rConnectionMock = mock(RConnection.class); 214 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock); 215 | 216 | rServeEngineProviderService.shutdownPreviousRuningInstances(); 217 | 218 | verify(rConnectionMock, times(1)).shutdown(); 219 | verify(rConnectionFactory, times(1)).releaseConnection(rConnectionMock); 220 | } 221 | 222 | @Test 223 | public void shutdownPreviousRuningInstancesDoesNotPropagateAnyException() throws Exception{ 224 | 225 | when(rConnectionFactory.getConnection()).thenThrow(new RuntimeException("Test expception")); 226 | 227 | rServeEngineProviderService.shutdownPreviousRuningInstances(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/test/java/com/jfcorugedo/rserver/service/RServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.jfcorugedo.rserver.service; 2 | 3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.mockito.Matchers.any; 6 | import static org.mockito.Matchers.anyListOf; 7 | import static org.mockito.Matchers.anyVararg; 8 | import static org.mockito.Mockito.times; 9 | import static org.mockito.Mockito.verify; 10 | import static org.mockito.Mockito.when; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | import org.mockito.ArgumentCaptor; 18 | import org.mockito.InjectMocks; 19 | import org.mockito.Mock; 20 | import org.mockito.junit.MockitoJUnit; 21 | import org.mockito.junit.MockitoRule; 22 | import org.rosuda.REngine.REXP; 23 | import org.rosuda.REngine.REXPDouble; 24 | import org.rosuda.REngine.REXPGenericVector; 25 | import org.rosuda.REngine.REXPInteger; 26 | import org.rosuda.REngine.REXPString; 27 | import org.rosuda.REngine.RList; 28 | 29 | import com.jfcorugedo.rserver.engine.REngineProviderService; 30 | 31 | public class RServiceImplTest { 32 | 33 | @Rule 34 | public MockitoRule rule = MockitoJUnit.rule(); 35 | 36 | @Mock 37 | private REngineProviderService engineProvider; 38 | 39 | @InjectMocks 40 | private RServiceImpl service = new RServiceImpl(); 41 | 42 | @Test 43 | public void testBlockFunction() throws Exception{ 44 | 45 | when(engineProvider.blockFunction(any(REXPInteger.class), any(REXPDouble.class))).thenReturn(getTestReturnValue()); 46 | 47 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d})); 48 | 49 | assertThat(result).hasSize(2); 50 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}); 51 | } 52 | 53 | @Test 54 | public void testBlockFunctionWithSeveralVariables() throws Exception{ 55 | 56 | when(engineProvider.blockFunction(any(REXPInteger.class), anyVararg())).thenReturn(getTestReturnValue()); 57 | 58 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d}, new double[]{-1d,-2d,-3d,-4d})); 59 | 60 | assertThat(result).hasSize(2); 61 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}); 62 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(REXPDouble.class); 63 | verify(engineProvider, times(1)).blockFunction(any(REXPInteger.class), continuousValues.capture()); 64 | assertThat(continuousValues.getAllValues().get(0).asDoubles()).containsExactly(1,2,3,4); 65 | assertThat(continuousValues.getAllValues().get(1).asDoubles()).containsExactly(-1,-2,-3,-4); 66 | } 67 | 68 | @Test 69 | public void testBlockDiscreteFunction() throws Exception{ 70 | 71 | when(engineProvider.blockDiscreteFunction(any(REXPInteger.class), any(REXPString.class))).thenReturn(getTestReturnValue()); 72 | 73 | List result = service.groupDiscreteValues(buildList(new String[]{"A","B","C","D"})); 74 | 75 | assertThat(result).hasSize(2); 76 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}); 77 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(REXPString.class); 78 | verify(engineProvider, times(1)).blockDiscreteFunction(any(REXPInteger.class), discreteValues.capture()); 79 | assertThat(discreteValues.getValue().asStrings()).containsExactly("A", "B", "C", "D"); 80 | } 81 | 82 | @Test 83 | public void testBlockDiscreteFunctionWithSeveralVariables() throws Exception{ 84 | 85 | when(engineProvider.blockDiscreteFunction(any(REXPInteger.class), anyVararg())).thenReturn(getTestReturnValue()); 86 | 87 | List result = service.groupDiscreteValues(newList(new String[]{"A", "B", "C", "D"}, new String[]{"E", "F", "G", "H"})); 88 | 89 | assertThat(result).hasSize(2); 90 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}); 91 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(REXPString.class); 92 | verify(engineProvider, times(1)).blockDiscreteFunction(any(REXPInteger.class), discreteValues.capture()); 93 | assertThat(discreteValues.getAllValues().get(0).asStrings()).containsExactly("A", "B", "C", "D"); 94 | assertThat(discreteValues.getAllValues().get(1).asStrings()).containsExactly("E", "F", "G", "H"); 95 | } 96 | 97 | private REXP getTestReturnValue() { 98 | RList values = new RList(); 99 | values.add(new REXPString(new String[]{"1","2"})); 100 | values.add(new REXPString(new String[]{"3","4"})); 101 | values.add(new REXPDouble(new double[]{0.098,1.234})); 102 | REXPGenericVector vector = new REXPGenericVector(values); 103 | 104 | return vector; 105 | } 106 | 107 | @Test 108 | public void testBlockFunctionWithOddNumberOfValues() throws Exception{ 109 | 110 | when(engineProvider.blockFunction(any(REXPInteger.class), any(REXPDouble.class))).thenReturn(getTestReturnOddValues()); 111 | 112 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d,5d})); 113 | 114 | assertThat(result).hasSize(3); 115 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}, new int[]{5}); 116 | } 117 | 118 | private REXP getTestReturnOddValues() { 119 | RList values = new RList(); 120 | values.add(new REXPString(new String[]{"1","2","5"})); 121 | values.add(new REXPString(new String[]{"3","4",null})); 122 | values.add(new REXPDouble(new double[]{0.098,1.234, Double.NaN})); 123 | REXPGenericVector vector = new REXPGenericVector(values); 124 | 125 | return vector; 126 | } 127 | 128 | @Test 129 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableCallsEngine() { 130 | 131 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 132 | 133 | List result = service.groupMultipleValues( 134 | buildList(new String[]{"A", "B", "C", "D"}), 135 | newList(new double[]{1d,2d,3d,4d}) 136 | ); 137 | 138 | verify(engineProvider, times(1)) 139 | .blockGeneralFunction( 140 | any(REXPInteger.class), 141 | anyListOf(REXPString.class), 142 | anyListOf(REXPDouble.class) 143 | ); 144 | 145 | assertThat(result).hasSize(2); 146 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}); 147 | } 148 | 149 | private void configureMockEngineBlockGeneralFunctionToReturnEvenResults() { 150 | when( 151 | engineProvider 152 | .blockGeneralFunction(any(REXPInteger.class), 153 | anyListOf(REXPString.class), 154 | anyListOf(REXPDouble.class)) 155 | ).thenReturn(getTestReturnValue()); 156 | } 157 | 158 | @Test 159 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesIds() { 160 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 161 | 162 | service.groupMultipleValues( 163 | buildList(new String[]{"A", "B", "C", "D"}), 164 | newList(new double[]{1d,2d,3d,4d}) 165 | ); 166 | 167 | ArgumentCaptor ids = ArgumentCaptor.forClass(REXPInteger.class); 168 | verify(engineProvider, times(1)) 169 | .blockGeneralFunction( 170 | ids.capture(), 171 | anyListOf(REXPString.class), 172 | anyListOf(REXPDouble.class) 173 | ); 174 | 175 | assertThat(ids.getAllValues()).hasSize(1); 176 | assertThat(ids.getValue().asIntegers()).containsExactly(0, 1, 2, 3); 177 | } 178 | 179 | @SuppressWarnings({ "unchecked", "rawtypes" }) 180 | @Test 181 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesDiscreteNativeValues() { 182 | 183 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 184 | 185 | service.groupMultipleValues( 186 | buildList(new String[]{"A", "B", "C", "D"}), 187 | newList(new double[]{1d,2d,3d,4d}) 188 | ); 189 | 190 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(List.class); 191 | verify(engineProvider, times(1)) 192 | .blockGeneralFunction( 193 | any(REXPInteger.class), 194 | discreteValues.capture(), 195 | anyListOf(REXPDouble.class) 196 | ); 197 | 198 | assertThat(discreteValues.getAllValues()).hasSize(1); 199 | assertThat(discreteValues.getValue()).hasSize(1); 200 | assertThat(discreteValues.getValue().get(0)).isInstanceOf(REXPString.class); 201 | assertThat(((REXPString)discreteValues.getValue().get(0)).asStrings()).containsExactly("A","B","C","D"); 202 | } 203 | 204 | @SuppressWarnings({ "unchecked", "rawtypes" }) 205 | @Test 206 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesContinuousNativeValues() { 207 | 208 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 209 | 210 | service.groupMultipleValues( 211 | buildList(new String[]{"A", "B", "C", "D"}), 212 | newList(new double[]{1d,2d,3d,4d}) 213 | ); 214 | 215 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(List.class); 216 | verify(engineProvider, times(1)) 217 | .blockGeneralFunction( 218 | any(REXPInteger.class), 219 | anyListOf(REXPString.class), 220 | continuousValues.capture() 221 | ); 222 | 223 | assertThat(continuousValues.getAllValues()).hasSize(1); 224 | assertThat(continuousValues.getValue()).hasSize(1); 225 | assertThat(continuousValues.getValue().get(0)).isInstanceOf(REXPDouble.class); 226 | assertThat(((REXPDouble)continuousValues.getValue().get(0)).asDoubles()).containsExactly(1,2,3,4); 227 | } 228 | 229 | @SuppressWarnings({ "unchecked", "rawtypes" }) 230 | @Test 231 | public void testBlockFunctionUsingSeveralDiscreteAndOneContinuousVariableGeneratesDiscreteNativeValues() { 232 | 233 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 234 | 235 | service.groupMultipleValues( 236 | buildList( 237 | new String[]{"A", "B", "C", "D"}, 238 | new String[]{"10", "20", "30", "40"} 239 | ), 240 | newList(new double[]{1d,2d,3d,4d}) 241 | ); 242 | 243 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(List.class); 244 | verify(engineProvider, times(1)) 245 | .blockGeneralFunction( 246 | any(REXPInteger.class), 247 | discreteValues.capture(), 248 | anyListOf(REXPDouble.class) 249 | ); 250 | 251 | assertThat(discreteValues.getAllValues()).hasSize(1); 252 | assertThat(discreteValues.getValue()).hasSize(2); 253 | assertThat(discreteValues.getValue().get(0)).isInstanceOf(REXPString.class); 254 | assertThat(((REXPString)discreteValues.getValue().get(0)).asStrings()).containsExactly("A","B","C","D"); 255 | assertThat(((REXPString)discreteValues.getValue().get(1)).asStrings()).containsExactly("10","20","30","40"); 256 | } 257 | 258 | @SuppressWarnings({ "unchecked", "rawtypes" }) 259 | @Test 260 | public void testBlockFunctionUsingOneDiscreteAndSeveralContinuousVariableGeneratesContinuousNativeValues() { 261 | 262 | configureMockEngineBlockGeneralFunctionToReturnEvenResults(); 263 | 264 | service.groupMultipleValues( 265 | buildList(new String[]{"A", "B", "C", "D"}), 266 | newList( 267 | new double[]{1d,2d,3d,4d}, 268 | new double[]{-1d,-2d,-3d,-4d} 269 | ) 270 | ); 271 | 272 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(List.class); 273 | verify(engineProvider, times(1)) 274 | .blockGeneralFunction( 275 | any(REXPInteger.class), 276 | anyListOf(REXPString.class), 277 | continuousValues.capture() 278 | ); 279 | 280 | assertThat(continuousValues.getAllValues()).hasSize(1); 281 | assertThat(continuousValues.getValue()).hasSize(2); 282 | assertThat(continuousValues.getValue().get(0)).isInstanceOf(REXPDouble.class); 283 | assertThat(((REXPDouble)continuousValues.getValue().get(0)).asDoubles()).containsExactly(1,2,3,4); 284 | assertThat(((REXPDouble)continuousValues.getValue().get(1)).asDoubles()).containsExactly(-1,-2,-3,-4); 285 | } 286 | 287 | @Test 288 | public void testKolmogorovSmirnovTest() { 289 | 290 | when(engineProvider.ksTest(any(REXPDouble.class), any(REXPDouble.class))).thenReturn(1.0); 291 | 292 | double pValue = service.kolmogorovSmirnovTest(new double[]{1d,2d,3d,4d}, new double[]{1d,2d,3d,4d}); 293 | 294 | assertThat(pValue).isGreaterThan(0.05); 295 | } 296 | 297 | 298 | private List buildList(String[]... strings) { 299 | List result = new ArrayList(); 300 | for(String[] value : strings) { 301 | result.add(value); 302 | } 303 | return result; 304 | } 305 | } 306 | --------------------------------------------------------------------------------