From d521147f60ffb53c0c297f8ec35de280f8f5c696 Mon Sep 17 00:00:00 2001 From: chenson42 Date: Tue, 4 Jun 2013 15:59:19 +0000 Subject: [PATCH] 0001052: Install a default ssl cert if none exists when ssl is turned on so that ssl works by default --- .../android/AndroidSymmetricEngine.java | 6 ++ .../symmetric/ClientSymmetricEngine.java | 10 ++- .../symmetric/AbstractSymmetricEngine.java | 5 +- .../symmetric/common/SystemConstants.java | 6 +- .../db/util/BasicDataSourceFactory.java | 3 +- symmetric-parent/pom.xml | 5 ++ .../src/main/deploy/security/keystore | Bin 1369 -> 32 bytes .../jumpmind/symmetric/SymmetricLauncher.java | 2 +- .../symmetric/SymmetricWebServer.java | 5 ++ .../symmetric/web/PullUriHandler.java | 43 +++++---- .../symmetric/web/ServerSymmetricEngine.java | 6 ++ .../symmetric/web/SymmetricEngineHolder.java | 3 +- symmetric-util/pom.xml | 6 ++ .../security/BouncyCastleSecurityService.java | 85 ++++++++++++++++++ .../jumpmind/security/ISecurityService.java | 2 + .../jumpmind/security/SecurityConstants.java | 12 ++- .../jumpmind/security/SecurityService.java | 31 ++++--- .../security/SecurityServiceFactory.java | 10 ++- .../java/org/jumpmind/util/AppUtilsTest.java | 1 - .../org/jumpmind/util/FormatUtilsTest.java | 1 - 20 files changed, 192 insertions(+), 50 deletions(-) create mode 100644 symmetric-util/src/main/java/org/jumpmind/security/BouncyCastleSecurityService.java diff --git a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java index cfa67cbc92..e902329571 100644 --- a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java +++ b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java @@ -27,6 +27,7 @@ import org.jumpmind.db.platform.IDatabasePlatform; import org.jumpmind.properties.TypedProperties; +import org.jumpmind.security.SecurityServiceFactory.SecurityServiceType; import org.jumpmind.symmetric.AbstractSymmetricEngine; import org.jumpmind.symmetric.ISymmetricEngine; import org.jumpmind.symmetric.ITypedPropertiesFactory; @@ -74,6 +75,11 @@ public AndroidSymmetricEngine(String registrationUrl, String externalId, String this.androidContext = androidContext; init(); } + + @Override + protected SecurityServiceType getSecurityServiceType() { + return SecurityServiceType.CLIENT; + } @Override protected ITypedPropertiesFactory createTypedPropertiesFactory() { diff --git a/symmetric-client/src/main/java/org/jumpmind/symmetric/ClientSymmetricEngine.java b/symmetric-client/src/main/java/org/jumpmind/symmetric/ClientSymmetricEngine.java index 0fbdb9d051..4e5a89e9f3 100644 --- a/symmetric-client/src/main/java/org/jumpmind/symmetric/ClientSymmetricEngine.java +++ b/symmetric-client/src/main/java/org/jumpmind/symmetric/ClientSymmetricEngine.java @@ -53,6 +53,7 @@ import org.jumpmind.exception.IoException; import org.jumpmind.properties.TypedProperties; import org.jumpmind.security.SecurityServiceFactory; +import org.jumpmind.security.SecurityServiceFactory.SecurityServiceType; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.SystemConstants; import org.jumpmind.symmetric.common.TableConstants; @@ -155,6 +156,11 @@ public ClientSymmetricEngine() { public ClientSymmetricEngine(boolean registerEngine) { this((Properties) null, registerEngine); } + + @Override + protected SecurityServiceType getSecurityServiceType() { + return SecurityServiceType.CLIENT; + } @Override protected void init() { @@ -207,7 +213,7 @@ public synchronized void stop() { public static BasicDataSource createBasicDataSource(File propsFile) { TypedProperties properties = createTypedPropertiesFactory(propsFile, null).reload(); - return BasicDataSourceFactory.create(properties, SecurityServiceFactory.create(properties)); + return BasicDataSourceFactory.create(properties, SecurityServiceFactory.create(SecurityServiceType.CLIENT, properties)); } @Override @@ -226,7 +232,7 @@ public static IDatabasePlatform createDatabasePlatform(TypedProperties propertie if (dataSource == null) { String jndiName = properties.getProperty(ParameterConstants.DB_JNDI_NAME); if (StringUtils.isBlank(jndiName)) { - dataSource = BasicDataSourceFactory.create(properties, SecurityServiceFactory.create(properties)); + dataSource = BasicDataSourceFactory.create(properties, SecurityServiceFactory.create(SecurityServiceType.CLIENT, properties)); } else { try { log.info("Looking up datasource in jndi. The jndi name is {}", jndiName); diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java index c21d4da5df..b5eb786576 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java @@ -45,6 +45,7 @@ import org.jumpmind.properties.TypedProperties; import org.jumpmind.security.ISecurityService; import org.jumpmind.security.SecurityServiceFactory; +import org.jumpmind.security.SecurityServiceFactory.SecurityServiceType; import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.TableConstants; @@ -243,10 +244,12 @@ public static ISymmetricEngine findEngineByName(String name) { public void setDeploymentType(String deploymentType) { this.deploymentType = deploymentType; } + + protected abstract SecurityServiceType getSecurityServiceType(); protected void init() { this.propertiesFactory = createTypedPropertiesFactory(); - this.securityService = SecurityServiceFactory.create(propertiesFactory.reload()); + this.securityService = SecurityServiceFactory.create(getSecurityServiceType(), propertiesFactory.reload()); TypedProperties properties = this.propertiesFactory.reload(); this.platform = createDatabasePlatform(properties); this.parameterService = new ParameterService(platform, propertiesFactory, properties.get( diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/common/SystemConstants.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/common/SystemConstants.java index 57e7d62910..da6ad2dc1b 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/common/SystemConstants.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/common/SystemConstants.java @@ -20,6 +20,8 @@ */ package org.jumpmind.symmetric.common; +import org.jumpmind.security.SecurityConstants; + /** * These are properties that can be set only as Java System properties using * -D settings. @@ -37,7 +39,7 @@ public class SystemConstants { public static final String SYSPROP_DEFAULT_HTTP_PORT = "symmetric.default.http.port"; public static final String SYSPROP_DEFAULT_HTTPS_PORT = "symmetric.default.https.port"; public static final String SYSPROP_DEFAULT_JMX_PORT = "symmetric.default.jmx.port"; - public static final String SYSPROP_KEYSTORE_TYPE = "sym.keystore.type"; - public static final String SYSPROP_KEYSTORE_CERT_ALIAS = "sym.keystore.ssl.cert.alias"; + public static final String SYSPROP_KEYSTORE_TYPE = SecurityConstants.SYSPROP_KEYSTORE_TYPE; + public static final String SYSPROP_KEYSTORE_CERT_ALIAS = SecurityConstants.SYSPROP_KEYSTORE_CERT_ALIAS; } \ No newline at end of file diff --git a/symmetric-jdbc/src/main/java/org/jumpmind/db/util/BasicDataSourceFactory.java b/symmetric-jdbc/src/main/java/org/jumpmind/db/util/BasicDataSourceFactory.java index 420a7eded3..7b203cff99 100644 --- a/symmetric-jdbc/src/main/java/org/jumpmind/db/util/BasicDataSourceFactory.java +++ b/symmetric-jdbc/src/main/java/org/jumpmind/db/util/BasicDataSourceFactory.java @@ -32,13 +32,14 @@ import org.jumpmind.security.ISecurityService; import org.jumpmind.security.SecurityConstants; import org.jumpmind.security.SecurityServiceFactory; +import org.jumpmind.security.SecurityServiceFactory.SecurityServiceType; import org.slf4j.LoggerFactory; public class BasicDataSourceFactory { public static BasicDataSource create(TypedProperties properties) { - return create(properties, SecurityServiceFactory.create(properties)); + return create(properties, SecurityServiceFactory.create(SecurityServiceType.CLIENT, properties)); } public static BasicDataSource create(TypedProperties properties, diff --git a/symmetric-parent/pom.xml b/symmetric-parent/pom.xml index 7cd4efd740..2d98a258df 100644 --- a/symmetric-parent/pom.xml +++ b/symmetric-parent/pom.xml @@ -587,6 +587,11 @@ ant 1.8.1 + + bouncycastle + bcprov-jdk16 + 140 + commons-io diff --git a/symmetric-server/src/main/deploy/security/keystore b/symmetric-server/src/main/deploy/security/keystore index 3ab488d14f54e1da993a21dff6a98ecdde9618b5..c408465500cb0af9cfd1f7371422ef8899ae6725 100644 GIT binary patch literal 32 ncmezO_TO6u1_mY|W_Xm5=la|E{*s%M?mC{^wn--0;QAy0^AQhj literal 1369 zcmezO_TO6u1_mY|W&~sA;>uhgTVCRs&2ymmPJN-E7zh3zG&SL)|_Wl)FyHB0?^f9I`l-c3cj{ev0 zmL6|7yNZwha)YVJLX$mGos~0mC&b(jU-14*k51u!_Uy1^iqVJ>&rX&#t}}kTd1ZB}zI!TvNhGiSk|x>61=1|C{z+RL7F~Zfz0QIC zq)vm|B45+MWsmLz7|1=4+;OJ!+*h0BpJ)Bv$gry8uIi&*67iULlvyUrR)9uA8xb=oK&ulAU0s_NCLyWVEqJRl(TXNm{wk{6;+<$fxh zDQ8)FWX+BAEwW`NA35H+x5uZnKFIs@EM|HCCuySFBb=AYPt0c#VZ8lNhUc`8u>RM7 z;bps-cgxL|4!kC@e&WY70pB8?+FiAI=Wtl}UhePqw_+=|W!BcJ2Ryp<$Msv_)zWRh zoUgg^VeW8+aP=i%KT+1tUH@MZO`pA6JMT;oMkPy-TNkLRNZo)a_#+{ z%d389nR(W;{hAjS`g_0CdauVv<4oL~rXSDDSNJn!Z|DPU?ME@;Cssbm@Ay@>T=bY^ zF4N153+A)Uh1ImJCvj~zI{W9fcUj$<%&<+K#t%%+pMNyxz==zu+S@;bbf4YA$-%WD zC8_W7%e;^IOJ5#2_{h)d>2t~3tU+SC_OK?$9b?(};ZW_3*MEM+E;HH~Z)EdCq%pnF zR{X&A3un83Ct7Fy`n0Ba_V?2Bg&f6fsh0iOGyLNsKM8$t5Gn7yezk6~rMSz-6=I*G zL+?Il;N-oiQ-4HIviH5!hP`K3A8$Rv5thH~)w0%K_n$leJh46~YTDV}aB zC2uJ_HZR5TU2E&d!jFQhWRpVz)&5<5QE_;!?fd+6&A=Ug;MB|-p=WAf2~5W^z;qm8 z(8PF@5qwPB{BO?ndgF&&O05Fj=hqABMY+|-hy%w(5fLm>kJkSMb-XHI@{VopYW zafyMPIIn@FfuWI^p@p%riA9t+uQ8BoXbI&GjH4RoBZmzzvokmLG8i;=GBq|bET2)- zcROWnAA|FeE{4S!S}A*X*vPz;>GGDba%{T5maKfv+E6)dUhF>uo5p9?C6gVE4c5Ld zJ?attJ79j^g76K!GtWlR_~DOyCZO+etzz+1&Hr^G|s2`v^~o({1nh>X%$>=HFg_xYtUq+4Olog4j provided + + + bouncycastle + bcprov-jdk16 + provided + junit junit diff --git a/symmetric-util/src/main/java/org/jumpmind/security/BouncyCastleSecurityService.java b/symmetric-util/src/main/java/org/jumpmind/security/BouncyCastleSecurityService.java new file mode 100644 index 0000000000..409d03d005 --- /dev/null +++ b/symmetric-util/src/main/java/org/jumpmind/security/BouncyCastleSecurityService.java @@ -0,0 +1,85 @@ +package org.jumpmind.security; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStore.Entry; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.bouncycastle.jce.PKCS10CertificationRequest; +import org.bouncycastle.x509.X509V1CertificateGenerator; +import org.jumpmind.util.AppUtils; + +public class BouncyCastleSecurityService extends SecurityService { + + public KeyPair generateRSAKeyPair() throws Exception { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); + kpGen.initialize(1024, new SecureRandom()); + return kpGen.generateKeyPair(); + } + + public X509Certificate generateV1Certificate(String host, KeyPair pair) throws Exception { + + host = host == null ? AppUtils.getHostName() : host; + String certString = String.format( + "CN=%s, OU=SymmetricDS, O=JumpMind, L=Unknown, ST=Unknown, C=Unknown", host); + log.info("Installing a default SSL certificate: {}", certString); + + X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setIssuerDN(new X500Principal(certString)); + certGen.setNotBefore(new Date(System.currentTimeMillis() - 86400000)); + certGen.setNotAfter(new Date(System.currentTimeMillis() + 788400000000l)); + certGen.setSubjectDN(new X500Principal(certString)); + certGen.setPublicKey(pair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + return certGen.generate(pair.getPrivate(), "BC"); + } + + public PKCS10CertificationRequest generateRequest(KeyPair pair) throws Exception { + return new PKCS10CertificationRequest("SHA256withRSA", new X500Principal( + "CN=Requested Test Certificate"), pair.getPublic(), null, pair.getPrivate()); + } + + @Override + public void installDefaultSslCert(String host) { + synchronized (BouncyCastleSecurityService.class) { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + + try { + KeyStore keyStore = getKeyStore(getKeyStorePassword()); + KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection( + getKeyStorePassword().toCharArray()); + String alias = System.getProperty(SecurityConstants.SYSPROP_KEYSTORE_CERT_ALIAS, + SecurityConstants.ALIAS_SYM_PRIVATE_KEY); + Entry entry = keyStore.getEntry(alias, param); + if (entry == null) { + KeyPair pair = generateRSAKeyPair(); + X509Certificate cert = generateV1Certificate(host, pair); + + X509Certificate[] serverChain = new X509Certificate[] { cert }; + + keyStore.setEntry(alias, new KeyStore.PrivateKeyEntry(pair.getPrivate(), + serverChain), param); + + saveKeyStore(keyStore, getKeyStorePassword()); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + } + +} diff --git a/symmetric-util/src/main/java/org/jumpmind/security/ISecurityService.java b/symmetric-util/src/main/java/org/jumpmind/security/ISecurityService.java index 367dfb7e06..8ce27f87f3 100644 --- a/symmetric-util/src/main/java/org/jumpmind/security/ISecurityService.java +++ b/symmetric-util/src/main/java/org/jumpmind/security/ISecurityService.java @@ -28,6 +28,8 @@ public interface ISecurityService { public void init(); + public void installDefaultSslCert(String host); + public String nextSecureHexString(int len); public String encrypt(String plainText); diff --git a/symmetric-util/src/main/java/org/jumpmind/security/SecurityConstants.java b/symmetric-util/src/main/java/org/jumpmind/security/SecurityConstants.java index ea169faafc..bb252eb7d7 100644 --- a/symmetric-util/src/main/java/org/jumpmind/security/SecurityConstants.java +++ b/symmetric-util/src/main/java/org/jumpmind/security/SecurityConstants.java @@ -22,6 +22,14 @@ public class SecurityConstants { + public static final String SYSPROP_KEYSTORE_TYPE = "sym.keystore.type"; + + public static final String SYSPROP_KEYSTORE_CERT_ALIAS = "sym.keystore.ssl.cert.alias"; + + public static final String SYSPROP_KEYSTORE = "sym.keystore.file"; + + public static final String SYSPROP_KEYSTORE_PASSWORD = "javax.net.ssl.keyStorePassword"; + public final static String CLASS_NAME_SECURITY_SERVICE = "security.service.class.name"; public static final String PREFIX_ENC = "enc:"; @@ -44,9 +52,5 @@ public class SecurityConstants { public static final String ALIAS_SYM_SECRET_KEY = "sym.secret"; public static final String EMBEDDED_WEBSERVER_DEFAULT_ROLE="symmetric"; - - public static final String SYSPROP_KEYSTORE = "sym.keystore.file"; - - public static final String SYSPROP_KEYSTORE_PASSWORD = "javax.net.ssl.keyStorePassword"; } \ No newline at end of file diff --git a/symmetric-util/src/main/java/org/jumpmind/security/SecurityService.java b/symmetric-util/src/main/java/org/jumpmind/security/SecurityService.java index 9f2f655aab..b2b22e4493 100644 --- a/symmetric-util/src/main/java/org/jumpmind/security/SecurityService.java +++ b/symmetric-util/src/main/java/org/jumpmind/security/SecurityService.java @@ -38,6 +38,7 @@ import javax.crypto.spec.PBEParameterSpec; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.NotImplementedException; import org.jumpmind.exception.IoException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,13 +53,17 @@ public class SecurityService implements ISecurityService { protected SecretKey secretKey; protected SecureRandom secRand; - + protected SecurityService() { } public void init() { } - + + public void installDefaultSslCert(String host) { + throw new NotImplementedException(); + } + protected void checkThatKeystoreFileExists() { String keyStoreLocation = System.getProperty(SecurityConstants.SYSPROP_KEYSTORE); if (!new File(keyStoreLocation).exists()) { @@ -67,7 +72,7 @@ protected void checkThatKeystoreFileExists() { + keyStoreLocation); } } - + public String encrypt(String plainText) { try { checkThatKeystoreFileExists(); @@ -76,19 +81,19 @@ public String encrypt(String plainText) { return new String(Base64.encodeBase64(enc), SecurityConstants.CHARSET); } catch (RuntimeException e) { throw e; - } catch (Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } public String decrypt(String encText) { try { - checkThatKeystoreFileExists(); + checkThatKeystoreFileExists(); byte[] dec = Base64.decodeBase64(encText.getBytes()); byte[] bytes = getCipher(Cipher.DECRYPT_MODE).doFinal(dec); return new String(bytes, SecurityConstants.CHARSET); } catch (RuntimeException e) { - throw e; + throw e; } catch (Exception e) { throw new RuntimeException(e); } @@ -108,8 +113,8 @@ protected Cipher getCipher(int mode) throws Exception { protected void initializeCipher(Cipher cipher, int mode) throws Exception { AlgorithmParameterSpec paramSpec = Cipher.getMaxAllowedParameterSpec(cipher.getAlgorithm()); - if (paramSpec instanceof PBEParameterSpec || - (paramSpec == null && cipher.getAlgorithm().startsWith("PBE"))) { + if (paramSpec instanceof PBEParameterSpec + || (paramSpec == null && cipher.getAlgorithm().startsWith("PBE"))) { paramSpec = new PBEParameterSpec(SecurityConstants.SALT, SecurityConstants.ITERATION_COUNT); cipher.init(mode, secretKey, paramSpec); @@ -121,9 +126,14 @@ protected void initializeCipher(Cipher cipher, int mode) throws Exception { } } - protected SecretKey getSecretKey() throws Exception { + protected String getKeyStorePassword() { String password = System.getProperty(SecurityConstants.SYSPROP_KEYSTORE_PASSWORD); password = (password != null) ? password : SecurityConstants.KEYSTORE_PASSWORD; + return password; + } + + protected SecretKey getSecretKey() throws Exception { + String password = getKeyStorePassword(); KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(password.toCharArray()); KeyStore ks = getKeyStore(password); KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) ks.getEntry( @@ -182,7 +192,8 @@ protected SecretKey getDefaultSecretKey() throws Exception { String keyPassword = nextSecureHexString(8); KeySpec keySpec = new PBEKeySpec(keyPassword.toCharArray(), SecurityConstants.SALT, SecurityConstants.ITERATION_COUNT, 56); - SecretKey secretKey = SecretKeyFactory.getInstance(SecurityConstants.ALGORITHM).generateSecret(keySpec); + SecretKey secretKey = SecretKeyFactory.getInstance(SecurityConstants.ALGORITHM) + .generateSecret(keySpec); return secretKey; } diff --git a/symmetric-util/src/main/java/org/jumpmind/security/SecurityServiceFactory.java b/symmetric-util/src/main/java/org/jumpmind/security/SecurityServiceFactory.java index 7edec98fd7..33656d8825 100644 --- a/symmetric-util/src/main/java/org/jumpmind/security/SecurityServiceFactory.java +++ b/symmetric-util/src/main/java/org/jumpmind/security/SecurityServiceFactory.java @@ -23,18 +23,20 @@ import org.jumpmind.properties.TypedProperties; public class SecurityServiceFactory { + + public enum SecurityServiceType { CLIENT, SERVER } public static ISecurityService create() { - return create(null); + return create(SecurityServiceType.CLIENT, null); } - - public static ISecurityService create(TypedProperties properties) { + + public static ISecurityService create(SecurityServiceType serviceType, TypedProperties properties) { try { if (properties == null) { properties = new TypedProperties(System.getProperties()); } String className = properties.get(SecurityConstants.CLASS_NAME_SECURITY_SERVICE, - SecurityService.class.getName()); + serviceType == SecurityServiceType.SERVER ? "org.jumpmind.security.BouncyCastleSecurityService" : SecurityService.class.getName()); ISecurityService securityService = (ISecurityService) Class.forName(className).newInstance(); securityService.init(); return securityService; diff --git a/symmetric-util/src/test/java/org/jumpmind/util/AppUtilsTest.java b/symmetric-util/src/test/java/org/jumpmind/util/AppUtilsTest.java index 0188c03e3f..b72b73f309 100644 --- a/symmetric-util/src/test/java/org/jumpmind/util/AppUtilsTest.java +++ b/symmetric-util/src/test/java/org/jumpmind/util/AppUtilsTest.java @@ -23,7 +23,6 @@ import java.util.Date; import org.apache.commons.lang.time.DateUtils; -import org.jumpmind.util.AppUtils; import org.junit.Assert; import org.junit.Test; diff --git a/symmetric-util/src/test/java/org/jumpmind/util/FormatUtilsTest.java b/symmetric-util/src/test/java/org/jumpmind/util/FormatUtilsTest.java index c0c2a32328..6eeb7a353a 100644 --- a/symmetric-util/src/test/java/org/jumpmind/util/FormatUtilsTest.java +++ b/symmetric-util/src/test/java/org/jumpmind/util/FormatUtilsTest.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.Map; -import org.jumpmind.util.FormatUtils; import org.junit.Assert; import org.junit.Test;