From a6411dbc846a40bb2e358d77f2ecc79b1da1c1fc Mon Sep 17 00:00:00 2001 From: Andrea Scardigli Date: Tue, 19 Nov 2024 11:41:25 +0100 Subject: [PATCH] release-2.1.0 (#3) * Update project to latest tag parer-kettle-2.1.0 * Update pom.xml * Update SNAPSHOT version * [maven-release-plugin] prepare release parer-kettle-2.1.0 * [maven-release-plugin] prepare for next development iteration --------- Co-authored-by: parerworker Co-authored-by: Stefano Sinatti Co-authored-by: GitHub Actions --- CHANGELOG.md | 12 +++ README.md | 6 +- RELEASE-NOTES.md | 16 ++-- parer-kettle-jpa/pom.xml | 2 +- parer-kettle-model/pom.xml | 2 +- parer-kettle-rest-client/pom.xml | 2 +- parer-kettle-rest/pom.xml | 2 +- parer-kettle-server/pom.xml | 13 ++- .../dao/MonitoraggioRepository.java | 5 +- .../persistence/service/DataServiceImpl.java | 16 +++- .../service/GestoreTrasformazioniImpl.java | 13 +-- .../parer/kettle/ws/client/S3ClientBean.java | 95 +++++++++++-------- parer-kettle-service/pom.xml | 2 +- parer-kettle-soap-client/pom.xml | 12 ++- parer-kettle-soap/pom.xml | 2 +- pom.xml | 43 +++++---- 16 files changed, 154 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec5d9f..7b1b5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ +## 2.1.0 (11-11-2024) + +### Bugfix: 2 +- [#34198](https://parermine.regione.emilia-romagna.it/issues/34198) Lista di risultati troncati dalla chiamata allo storico delle trasformazioni +- [#34064](https://parermine.regione.emilia-romagna.it/issues/34064) Gestione del carattere + nel nome dell'oggetto + +### Novità: 1 +- [#34451](https://parermine.regione.emilia-romagna.it/issues/34451) Rimuovere il parametro XF_KETTLE_DB_PASSWORD dal report di trasformazione + +### SUE: 1 +- [#34063](https://parermine.regione.emilia-romagna.it/issues/34063) Modifica all'url di AWS nella configurazione di kettle server (tutti gli ambienti) + ## 2.0.0 (24-04-2024) ### Bugfix: 1 diff --git a/README.md b/README.md index 7fa757b..c834acc 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ I servizi REST esposti sono i seguenti: I servizi SOAP esposti sono i seguenti: -- **esistenzaCartella** : controlla l'esistenza di una determninata cartella nel repository di kettle. +- **esistenzaCartella** : controlla l'esistenza di una determinata cartella nel repository di kettle. - **inserisciTransformation** : aggiunge una nuova trasformazione nel repository kettle. - **statusCodaTrasformazione** : resituisce un immagine delle trasformazione in corso, in coda o eseguite. -- **inserisciJob** : aggiungi un nuovo job nel repository kettle. +- **inserisciJob** : aggiunge un nuovo job nel repository kettle. - **eseguiTrasformazione** : esegue una trasformazione presente nel repository. - **inserisciCartella** : crea una nuova cartella nel repository di kettle. -- **ottieniParametri** : ottiene una lista dei parametri di una determninata trasformazione. +- **ottieniParametri** : ottiene una lista dei parametri di una determinata trasformazione. - **eliminaCartella** : elimina una cartella nel repository di kettle. [Qui](src/docs/kettleserver.wsdl) il wsdl dell'endpoint SOAP. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 196e46b..d52fed1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,9 +1,11 @@ -## 2.0.0 (24-04-2024) +## 2.1.0 (11-11-2024) -### Bugfix: 1 -- [#27975](https://parermine.regione.emilia-romagna.it/issues/27975) Correzione della gestione dell'avvio di kettle senza la presenza di object storage +### Bugfix: 2 +- [#34198](https://parermine.regione.emilia-romagna.it/issues/34198) Lista di risultati troncati dalla chiamata allo storico delle trasformazioni +- [#34064](https://parermine.regione.emilia-romagna.it/issues/34064) Gestione del carattere + nel nome dell'oggetto -### Novità: 3 -- [#30870](https://parermine.regione.emilia-romagna.it/issues/30870) Migrazione plugin Kettle alla versione 9.4 -- [#29136](https://parermine.regione.emilia-romagna.it/issues/29136) Migrazione Kettle alla versione 9.4 -- [#25567](https://parermine.regione.emilia-romagna.it/issues/25567) Gestione esterna al pacchetto di deploy per le configurazioni di Kettle +### Novità: 1 +- [#34451](https://parermine.regione.emilia-romagna.it/issues/34451) Rimuovere il parametro XF_KETTLE_DB_PASSWORD dal report di trasformazione + +### SUE: 1 +- [#34063](https://parermine.regione.emilia-romagna.it/issues/34063) Modifica all'url di AWS nella configurazione di kettle server (tutti gli ambienti) diff --git a/parer-kettle-jpa/pom.xml b/parer-kettle-jpa/pom.xml index 4903718..2722f44 100644 --- a/parer-kettle-jpa/pom.xml +++ b/parer-kettle-jpa/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-jpa Parer Kettle Persistence diff --git a/parer-kettle-model/pom.xml b/parer-kettle-model/pom.xml index 28b4a36..97aa519 100644 --- a/parer-kettle-model/pom.xml +++ b/parer-kettle-model/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-model Parer Kettle Model diff --git a/parer-kettle-rest-client/pom.xml b/parer-kettle-rest-client/pom.xml index 104b220..3f8cc3a 100644 --- a/parer-kettle-rest-client/pom.xml +++ b/parer-kettle-rest-client/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-rest-client Parer Kettle REST Client diff --git a/parer-kettle-rest/pom.xml b/parer-kettle-rest/pom.xml index 5374ad0..b7b6c4f 100644 --- a/parer-kettle-rest/pom.xml +++ b/parer-kettle-rest/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-rest Parer Kettle REST Service diff --git a/parer-kettle-server/pom.xml b/parer-kettle-server/pom.xml index 6403f82..7e6f3fb 100644 --- a/parer-kettle-server/pom.xml +++ b/parer-kettle-server/pom.xml @@ -3,7 +3,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT kettle-server @@ -137,10 +137,19 @@ - + + + + software.amazon.awssdk + s3 + + software.amazon.awssdk + apache-client + joda-time joda-time diff --git a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/dao/MonitoraggioRepository.java b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/dao/MonitoraggioRepository.java index 0dd407f..5978edb 100644 --- a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/dao/MonitoraggioRepository.java +++ b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/dao/MonitoraggioRepository.java @@ -64,7 +64,8 @@ List findByNmKsInstanceAndTiStatoTrasf(String nmKsInstance, public long countByNmKsInstanceAndTiStatoTrasfIn(String nmKsInstance, MonExecTrasf.STATO_TRASFORMAZIONE... tiStatoTrasf); - public Slice findByNmKsInstanceAndDtInizioTrasfBetweenAndTiStatoTrasfIn(Pageable pageable, - String nmKsInstance, Date startDate, Date endDate, MonExecTrasf.STATO_TRASFORMAZIONE... tiStatoTrasf); + public Slice findByNmKsInstanceAndDtInizioTrasfBetweenAndTiStatoTrasfInOrderByDtInizioTrasfDesc( + Pageable pageable, String nmKsInstance, Date startDate, Date endDate, + MonExecTrasf.STATO_TRASFORMAZIONE... tiStatoTrasf); } diff --git a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/DataServiceImpl.java b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/DataServiceImpl.java index 54736f3..0f224b2 100644 --- a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/DataServiceImpl.java +++ b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/DataServiceImpl.java @@ -27,6 +27,7 @@ import it.eng.parer.kettle.server.persistence.lite.dao.ReportRepository; import it.eng.parer.kettle.service.DataService; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; @@ -61,6 +62,9 @@ public class DataServiceImpl implements DataService { @Autowired private ReportRepository reportRepository; + // MEV34451 TODO, leggere da file. + private final String[] blackListedParams = { "XF_KETTLE_DB_PASSWORD" }; + @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean accettaTrasformazione(Trasformazione trasformazione) { @@ -140,6 +144,12 @@ public void iniziaTrasformazione(Trasformazione trasformazione) { StringBuilder parameters = new StringBuilder(); for (Parametro parametro : trasformazione.getParametri()) { + + if (Arrays.stream(blackListedParams).anyMatch(parametro.getNomeParametro()::equals)) { + // MEV34451 salta i parametri blacklistati. + continue; + } + parameters = parameters.length() > 0 ? parameters.append(" | ").append(parametro.getNomeParametro()).append(" : ") .append(parametro.getValoreParametro()) @@ -396,9 +406,9 @@ public List getStoricoTrasformazioni(Date startDate, Date e List storicoTrasformazioni = new ArrayList<>(); List monitoraggi = monitoraggioRepository - .findByNmKsInstanceAndDtInizioTrasfBetweenAndTiStatoTrasfIn(PageRequest.of(0, numResults), - ottieniParametroConfigurazione("config.instance_name"), startDate, endDate, - MonExecTrasf.STATO_TRASFORMAZIONE.ERRORE_TRASFORMAZIONE, + .findByNmKsInstanceAndDtInizioTrasfBetweenAndTiStatoTrasfInOrderByDtInizioTrasfDesc( + PageRequest.of(0, numResults), ottieniParametroConfigurazione("config.instance_name"), + startDate, endDate, MonExecTrasf.STATO_TRASFORMAZIONE.ERRORE_TRASFORMAZIONE, MonExecTrasf.STATO_TRASFORMAZIONE.TRASFORMAZIONE_TERMINATA) .getContent(); diff --git a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/GestoreTrasformazioniImpl.java b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/GestoreTrasformazioniImpl.java index 9eb6b97..c102e32 100644 --- a/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/GestoreTrasformazioniImpl.java +++ b/parer-kettle-server/src/main/java/it/eng/parer/kettle/server/persistence/service/GestoreTrasformazioniImpl.java @@ -17,7 +17,6 @@ package it.eng.parer.kettle.server.persistence.service; -import com.amazonaws.services.s3.model.S3Object; import it.eng.parer.kettle.model.KettleCrudException; import it.eng.parer.kettle.model.KettleJob; import it.eng.parer.kettle.model.KettleTransformation; @@ -34,8 +33,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.List; @@ -63,6 +62,8 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; /** * @@ -481,7 +482,6 @@ private String preparaTrasformazioneDaObjectStorage(Trasformazione trasformazion + trasformazione.getIdOggettoPing()); } - S3Object s3Object = s3Client.getObject(oSBucket, oSKey); File targetFileDirectory = new File(targetFileDestDirectory + File.separator + "INPUT_FILE", FilenameUtils.getBaseName(targetFileDest)); File targetFile = new File(targetFileDirectory, FilenameUtils.getName(targetFileDest)); @@ -490,11 +490,12 @@ private String preparaTrasformazioneDaObjectStorage(Trasformazione trasformazion targetFileDirectory.mkdirs(); } - try (InputStream in = s3Object.getObjectContent(); OutputStream os = new FileOutputStream(targetFile)) { - - IOUtils.copyLarge(in, os); + try (OutputStream os = new FileOutputStream(targetFile)) { + ResponseInputStream s3Object = s3Client.getObject(oSBucket, oSKey); + IOUtils.copyLarge(s3Object, os); } catch (Exception ex) { + LOGGER.error("Errore nel recupero del file da object storage per " + trasformazione.getIdOggettoPing(), ex); throw new KettleException( "Errore nel recupero del file da object storage per " + trasformazione.getIdOggettoPing()); } diff --git a/parer-kettle-server/src/main/java/it/eng/parer/kettle/ws/client/S3ClientBean.java b/parer-kettle-server/src/main/java/it/eng/parer/kettle/ws/client/S3ClientBean.java index ee77fce..2ca3163 100644 --- a/parer-kettle-server/src/main/java/it/eng/parer/kettle/ws/client/S3ClientBean.java +++ b/parer-kettle-server/src/main/java/it/eng/parer/kettle/ws/client/S3ClientBean.java @@ -17,27 +17,32 @@ package it.eng.parer.kettle.ws.client; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest; -import com.amazonaws.services.s3.model.CompleteMultipartUploadResult; -import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; -import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.UploadPartRequest; -import com.amazonaws.services.s3.model.UploadPartResult; import it.eng.parer.kettle.service.DataService; import java.io.File; +import java.net.URI; +import java.time.Duration; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; /** * @@ -51,7 +56,7 @@ public class S3ClientBean { @Autowired private DataService dataService; - private AmazonS3 awsClient; + private S3Client awsClient; private boolean isActiveFlag = false; @@ -72,12 +77,16 @@ private void init() { // Istanzio il client http (possiede le chiamate al protocollo Amazon S3) LOGGER.info("Sto per effettuare il collegamento all'endpoint S3 [ " + storageAddress + "]"); - BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretKey); - awsClient = AmazonS3Client.builder() - .withEndpointConfiguration( - new AwsClientBuilder.EndpointConfiguration(storageAddress, Regions.US_EAST_1.name())) - .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) - .withPathStyleAccessEnabled(Boolean.TRUE).build(); + + final AwsCredentialsProvider credProvider = StaticCredentialsProvider + .create(AwsBasicCredentials.create(accessKeyId, secretKey)); + + awsClient = S3Client.builder().endpointOverride(URI.create(storageAddress)).region(Region.US_EAST_1) + .credentialsProvider(credProvider).forcePathStyle(true) + .httpClientBuilder(ApacheHttpClient.builder().maxConnections(100) + .connectionTimeout(Duration.ofMinutes(1L)).socketTimeout(Duration.ofMinutes(10L))) + .build(); + LOGGER.info("########## CLIENT S3 INIZIALIZZATO ###################"); } else { LOGGER.info("########## CLIENT S3 DISATTIVO ###################"); @@ -91,42 +100,46 @@ private void init() { private void destroy() { LOGGER.info("Shutdown endpoint S3..."); if (awsClient != null) { - awsClient.shutdown(); + awsClient.close(); } } public void deleteObject(String bucketName, String key) { - awsClient.deleteObject(bucketName, key); + DeleteObjectRequest delOb = DeleteObjectRequest.builder().bucket(bucketName).key(key).build(); + awsClient.deleteObject(delOb); } - public S3Object getObject(String bucketName, String key) { - return awsClient.getObject(bucketName, key); - } + public ResponseInputStream getObject(String bucketName, String key) throws Exception { + try { + GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(key).build(); + return awsClient.getObject(getObjectRequest); - public boolean doesObjectExist(String bucketName, String key) { - return awsClient.doesObjectExist(bucketName, key); + } catch (AwsServiceException | SdkClientException e) { + LOGGER.error("impossibile ottenere dal bucket " + bucketName + " oggetto con chiave " + key, e); + throw new Exception("impossibile ottenere dal bucket " + bucketName + " oggetto con chiave " + key, e); + } } - public void putObject(String bucketName, String nomeFilePacchetto, File file) { - awsClient.putObject(bucketName, nomeFilePacchetto, file); - } + public boolean doesObjectExist(String bucketName, String key) { + HeadObjectRequest objectRequest = HeadObjectRequest.builder().key(key).bucket(bucketName).build(); - public void putObject(String bucketName, String nomeFilePacchetto, String content) { - awsClient.putObject(bucketName, nomeFilePacchetto, content); - } + try { + awsClient.headObject(objectRequest); + return true; - public InitiateMultipartUploadResult initiateMultipartUpload( - InitiateMultipartUploadRequest initiateMultipartUploadRequest) { - return awsClient.initiateMultipartUpload(initiateMultipartUploadRequest); + } catch (S3Exception e) { + return false; + } } - public CompleteMultipartUploadResult completeMultipartUpload( - CompleteMultipartUploadRequest completeMultipartUploadRequest) { - return awsClient.completeMultipartUpload(completeMultipartUploadRequest); + public void putObject(String bucketName, String nomeFilePacchetto, File file) { + PutObjectRequest putOb = PutObjectRequest.builder().bucket(bucketName).key(nomeFilePacchetto).build(); + awsClient.putObject(putOb, RequestBody.fromFile(file)); } - public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest) { - return awsClient.uploadPart(uploadPartRequest); + public void putObject(String bucketName, String nomeFilePacchetto, String content) { + PutObjectRequest putOb = PutObjectRequest.builder().bucket(bucketName).key(nomeFilePacchetto).build(); + awsClient.putObject(putOb, RequestBody.fromString(content)); } public boolean isActive() { diff --git a/parer-kettle-service/pom.xml b/parer-kettle-service/pom.xml index f810d6f..0b0b600 100644 --- a/parer-kettle-service/pom.xml +++ b/parer-kettle-service/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-service jar diff --git a/parer-kettle-soap-client/pom.xml b/parer-kettle-soap-client/pom.xml index faf09f8..0dc603d 100644 --- a/parer-kettle-soap-client/pom.xml +++ b/parer-kettle-soap-client/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-soap-client Parer Kettle SOAP Client @@ -23,6 +23,16 @@ com.sun.xml.ws jaxws-ri pom + + + com.sun.xml.ws + samples + + + com.sun.xml.ws + release-documentation + + \ No newline at end of file diff --git a/parer-kettle-soap/pom.xml b/parer-kettle-soap/pom.xml index 956722f..9e6543f 100644 --- a/parer-kettle-soap/pom.xml +++ b/parer-kettle-soap/pom.xml @@ -4,7 +4,7 @@ it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT parer-kettle-soap Parer Kettle SOAP Service diff --git a/pom.xml b/pom.xml index f8f16b3..debf165 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 it.eng.parer parer-kettle - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT pom it.eng.parer @@ -11,7 +11,12 @@ 4.2.0 - + + scm:git:https://github.com/RegioneER/parer-xformer-kettle-server.git + HEAD + + + github https://maven.pkg.github.com/RegioneER/parer-xformer-kettle-server @@ -25,12 +30,6 @@ - - scm:git:https://github.com/RegioneER/parer-xformer-kettle-server.git - HEAD - - - Parer Kettle Progetto che si occupa di implementare un'ambiente tomcat in cui effettuare le trasformazioni kettle. @@ -41,6 +40,7 @@ ${project.version} 2.1.6.RELEASE true + 2.26.3 @@ -49,37 +49,37 @@ it.eng.parer parer-kettle-model - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-jpa - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-service - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-soap - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-soap-client - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-rest - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT it.eng.parer parer-kettle-rest-client - 2.0.1-SNAPSHOT + 2.1.1-SNAPSHOT @@ -166,23 +166,30 @@ - + + + software.amazon.awssdk + bom + ${software.amazon.awssdk.version} + pom + import joda-time joda-time 2.10.8 - +