jhipster – use description in enums instead of key names

I used jhipster to generate an entity which have a property of type enum.

jhipster entity tipoEstudio
What are the values of your enumeration (separated by comma, no spaces)? SUPERIOR,MEDIA_SUPERIOR,BASICA

because i couldn’t use spaces or special characters (diacritics i.e. accents) in enum names I had to shorten the description a little bit.

It generated the following code

export const enum TipoEducativo {
    SUPERIOR = 'SUPERIOR',
    MEDIA_SUPERIOR = 'MEDIA_SUPERIOR',
    BASICA = 'BASICA'
}

export interface ITipoEstudio {
    id?: number;
    nombre?: string;
    tipoEducativo?: TipoEducativo;
}

export class TipoEstudio implements ITipoEstudio {
    constructor(public id?: number, public nombre?: string, public tipoEducativo?: TipoEducativo) {}
}

The problem was that the values stored in the database came as (note the spaces and diacritics)

    'EDUCACIÓN SUPERIOR',
    'EDUCACIÓN MEDIA SUPERIOR',
    'EDUCACIÓN BÁSICA'

In order to be able to use those values I had to first add a description property in the enum

public enum TipoEducativo {
    SUPERIOR("EDUCACIÓN SUPERIOR"), MEDIA_SUPERIOR("EDUCACIÓN MEDIA SUPERIOR"), BASICA("EDUCACIÓN BÁSICA");

    private String desc;

    TipoEducativo(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public static TipoEducativo getTipo(String desc){
        if(desc == null){
            return null;
        }

        for(TipoEducativo tipoEducativo: TipoEducativo.values()){
            if(desc.equals(tipoEducativo.desc)){
                return tipoEducativo;
            }
        }

        throw new IllegalArgumentException("No existe el TipoEducativo para " + desc);
    }

}

And then change the Java class for the Entity by removing the @Enumerated annotation and adding a converter for the enum field.

@Entity
@Table(name = "tipo_estudio")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class TipoEstudio implements Serializable {

    // id, constructor, etc

    @NotNull
    @Convert(converter = TipoEducativoConverter.class)
    @Column(name = "stipo_educativo", nullable = false)
    private TipoEducativo tipoEducativo;

    // getters, setters, etc

}

TipoEducativoConverter.java

@Converter
public class TipoEducativoConverter implements AttributeConverter<TipoEducativo, String> {


    @Override
    public String convertToDatabaseColumn(TipoEducativo tipoEducativo) {
        return tipoEducativo.getDesc();
    }

    @Override
    public TipoEducativo convertToEntityAttribute(String desc) {
        return TipoEducativo.getTipo(desc);
    }
}

With these changes the app was reading correctly the enum type field and was storing the correct description for its value. The only grip I had then was that in the web client UI the underscored descriptions were being showed instead of the long descriptions.

I thought that if I changed the description in the enum type it would use those in the UI, so i changed it like so

export const enum TipoEducativo {
    SUPERIOR = 'EDUCACIÓN SUPERIOR',
    MEDIA_SUPERIOR = 'EDUCACIÓN MEDIA SUPERIOR',
    BASICA = 'EDUCACIÓN BÁSICA'
}

but it changed nothing. I had to change the html and component as follows

<tr *ngFor="let tipoEstudio of tipoEstudios ;trackBy: trackId">
                <td><a [routerLink]="['/tipo-estudio', tipoEstudio.id, 'view' ]">{{tipoEstudio.id}}</a></td>
                <td>{{tipoEstudio.nombre}}</td>
                <td>{{TipoEducativo[tipoEstudio.tipoEducativo]}}</td>
                <!-- button columns --> 
            </tr>

tipo-estudio.component.ts

// other imports
import { ITipoEstudio, TipoEducativo } from 'app/shared/model/tipo-estudio.model';

@Component({
    selector: 'jhi-tipo-estudio',
    templateUrl: './tipo-estudio.component.html'
})
export class TipoEstudioComponent implements OnInit, OnDestroy {
    // other fields
    TipoEducativo = TipoEducativo;

    constructor() {}

    // other methods
}

which led to this error

error TS2476: A const enum member can only be accessed using a string literal.

the key thing is

const in an enum means the enum is fully erased, which means you can’t index it by an arbitrary value. Just remove the const modifier.

as explained here

Finally I removed the const keyword and was able to use the descriptions in the enum instead of key names.

Note however that you still have to replace the code generated by jhipster on select items since they’re hard coded like so.

<select class="form-control" name="tipoEducativo" [(ngModel)]="tipoEstudio.tipoEducativo" id="field_tipoEducativo"  required>
                        <option value="SUPERIOR"> EDUCACIÓN SUPERIOR</option>
                        <option value="MEDIA_SUPERIOR">EDUCACIÓN MEDIA SUPERIOR</option>
                        <option value="BASICA">EDUCACIÓN BÁSICA</option>
                    </select>

sources:
Best approach to JPA enumerated types mapping
Mapping enums with a fixed ID in JPA – An alternative to String and Ordinal
Mapping enums done right with @Convert in JPA 2.1
Get TypeScript enum name from instance
const enum in Typescript
How do the different enum variants work in TypeScript?

Anuncios

jpa – EclipseLink NetBeans Glassfish Exception with lambda functions in Entity

I had to fix a simple getter method for an Entity. I had to return unique elements in an array, so I thought it would be a very good idea to use a stream and the distinct method.

So, instead of having this

public List<String> getNucsAcumulados(){
        if (nucsAcumulados == null) {
            nucsAcumulados = new ArrayList<>();
            if (getCarpetasAcumuladas() != null && getCarpetasAcumuladas().size() > 0) {
                for (Carpeta carpeta : getCarpetasAcumuladas()) {
                    if (!nucsAcumulados.contains(carpeta.getNuc())) {
                        nucsAcumulados.add(carpeta.getNuc());
                    }
                }
            }
        }
        return nucsAcumulados;
    }

i was trying to use this

public List<String> getNucsAcumulados(){
        if (nucsAcumulados == null) {
            nucsAcumulados = getCarpetasAcumuladas().stream()
                    .map(Carpeta::getNuc).distinct().collect(Collectors.toList());
        }
        return nucsAcumulados;
    }

but this change threw me an obscure Exception that has nothing to do with it

Caused by: Exception [EclipseLink-7161] (Eclipse Persistence Services – 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Entity class [class mx.gob.queretaro.brujula.tsj.domain.carpetas.CuadernoExhortoRecibido] has no primary key specified. It should define either an @Id, @EmbeddedId or an @IdClass. If you have defined PK using any of these annotations then make sure that you do not have mixed access-type (both fields and properties annotated) in your entity class hierarchy.

the Class signaled by the Exception already has the Id annotation it was complaining about.

As it happens, if you’re using Glassfish / EclipseLink: EclipseLink <2.6.0 it is a known bug. And you must not use lambda annotations in your entities, period. So i just reverted back the changes to good old java and everything was fine again.

java – signature with private .key encrypted password protected file

The tax administration of my country (México) -Sistema de Administración Tributaria, SAT- gaves us a pair of private and public key for us to authenticate. These are in the form of a file with the .key extension and a file with the .cer extension, respectively. They’re in DER format and the private key is in PKCS8 while the certificate is of type X.509 according to this post.
You can convert your .key file to the PEM format using openssl

openssl pkcs8 -inform DER -in acde970801.key -out
acde970801.pem

We wanted to use this keys, to sign and verify signed documents, in their original format since those are what the end user have. I searched how to do this and found different suggestions. I first tried this post from stackoverflow How to decrypt a private key in Java (without BC openssl)

public PrivateKey decryptKey(byte[] pkcs8Data, char[] password) throws Exception {
    PBEKeySpec pbeSpec = new PBEKeySpec(password);
    EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(pkcs8Data);
    SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
    Key secret = skf.generateSecret(pbeSpec);
    PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(keySpec);
}

but it didn’t work, it threw the following exception

Caused by: java.io.IOException: ObjectIdentifier() — data isn’t an object ID (tag = 48)

The funny thing is that I converted the .key file to the PEM format with the above openssl command and then I tried the answer provided in Read RSA private key of format PKCS1 in JAVA and it worked, I just had to make a few changes

private static PrivateKey readPrivateKeyPEM(String filename) throws Exception {
        PEMParser pemParser = new PEMParser(new FileReader(filename));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
        PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
        PrivateKey privateKey = converter.getPrivateKey(privateKeyInfo);
        return privateKey;
    }

unfortunately this failed to comply our initial requirement of using the original .key file, also note here that the password is no longer required since we decrypted de key while doing the conversion/export to the PEM format.

Finally I found the solution in How read a PKCS8 encrypted Private key which is also encoded in DER with bouncycastle?.

First, we need to import the required libraries (Bouncy Castle). Note that jdk15on means from jdk 1.5 to 1.8 (java 8).


        
            org.bouncycastle
            bcprov-jdk15on
            1.60
        
        
            org.bouncycastle
            bcpkix-jdk15on
            1.60
        
    

and then we can use the classes to get our private key and do the signing and verification. Our final code is something like this

import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.util.encoders.Base64;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.nio.file.Files;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class Main {
    private static PrivateKey readPrivateKeyPEM(String filename) throws Exception {
        PEMParser pemParser = new PEMParser(new FileReader(filename));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
        PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
        PrivateKey privateKey = converter.getPrivateKey(privateKeyInfo);
        return privateKey;
    }

    private static PrivateKey readPrivateKeyDER(String filename, String password) throws Exception {
        ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(Files.readAllBytes(new File(filename).toPath()));
        PKCS8EncryptedPrivateKeyInfo pkcs8EncryptedPrivateKeyInfo = new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(asn1Sequence));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
        PrivateKeyInfo privateKeyInfo = pkcs8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptorProvider);
        PrivateKey privateKey = converter.getPrivateKey(privateKeyInfo);
        return privateKey;
    }


    private static X509Certificate readCertificate(String filename) throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) certificateFactory.generateCertificate(new FileInputStream(filename));
    }

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        //PrivateKey privateKey = readPrivateKeyPEM("/tmp/aecc820430qu5.pem");
        PrivateKey privateKey = readPrivateKeyDER("/tmp/aecc820430qu5.key", "s3cr3t");
        X509Certificate cert = readCertificate("/tmp/aecc820430qu5.cer");
        System.out.println("Private key algorithm: " + privateKey.getAlgorithm());
        System.out.println("Private key format: " + privateKey.getFormat());

        File file = new File("/tmp/acuerdo.odt");
        byte[] data = Files.readAllBytes(file.toPath());

        Signature signature = Signature.getInstance("SHA1WithRSA");
        signature.initSign(privateKey);
        signature.update(data);
        byte[] sign = signature.sign();
        System.out.println("Signature: " + new String(Base64.encode(sign)));

        signature.initVerify(cert);
        //signature.update(Files.readAllBytes(new File("/tmp/acuerdo-a.odt").toPath()));
        signature.update(data);
        System.out.println("Firma verificada: " + signature.verify(sign));
    }
}

great!

update(jan 23, 2019): although it worked in a clean empty project, in my current project it was throwing the exception

Caused by: org.bouncycastle.operator.OperatorCreationException: 1.2.840.113549.1.5.13 not available: PBKDF2with8BIT SecretKeyFactory not available
at org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder$1.get(Unknown Source)

I just had to add the provider in the readPrivateKeyDER method

private static PrivateKey readPrivateKeyDER(String filename, String password) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(Files.readAllBytes(new File(filename).toPath()));
        PKCS8EncryptedPrivateKeyInfo pkcs8EncryptedPrivateKeyInfo = new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(asn1Sequence));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
        PrivateKeyInfo privateKeyInfo = pkcs8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptorProvider);
        PrivateKey privateKey = converter.getPrivateKey(privateKeyInfo);
        return privateKey;
    }

java – detached entity passed to persist

We use jhipster as a template for one of our applications.
We have a related entity of User using a OneToOne relationship with extra information as suggested here.

The following code tries to find the user, which already must exist, and then find the related ‘extra’ information, a keyfile in this particular case, -if found- it should update it and if not it should create it and store it in the database.

@PostMapping("/key/{login:" + Constants.LOGIN_REGEX + "}")
    @Timed
    public ResponseEntity<UserKeyFile> updateKeyFile(@PathVariable String login, @RequestPart("file")MultipartFile file) {
        log.debug("peticion REST para actualizar el archivo key del usuario : {}", login);
        File fileSaved = saveToFile(file, login);

        return ResponseUtil.wrapOrNotFound(
            this.userRepository.findOneByLogin(login).flatMap(
                user -> Optional.of(
                    this.userKeyFileRepository.findById(user.getId()).map(userKeyFile -> {
                        userKeyFile.setLastModifiedDate(Instant.now());
                        return userKeyFile;
                    }).orElseGet(() -> {
                        UserKeyFile userKeyFile = new UserKeyFile();
                        userKeyFile.setUser(user);
                        userKeyFile.setRuta(fileSaved.getAbsolutePath());
                        return this.userKeyFileRepository.save(userKeyFile);
                    })
                )
            )
        );
    }

at a first attempt it threw the following error

2018-12-06 09:10:15.825 ERROR 14686 — [ XNIO-2 task-3] m.g.t.aop.logging.LoggingAspect : Exception in com.contoso.web.rest.UserKeyFileResource.updateKeyFile() with cause = ‘org.hibernate.PersistentObjectException: detached entity passed to persist: com.contoso.domain.User’ and exception = ‘detached entity passed to persist: com.contoso.domain.User; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.contoso.domain.User

as it happens, we were using two different repositories and hence the error. We just need to add the org.springframework.transaction.annotation.Transactional annotation

@Transactional
public ResponseEntity updateKeyFile(@PathVariable String login, @RequestPart(“file”)MultipartFile file) {

and now we can persist our related Entity. great!

jhipster – configure elasticsearch analyzer for a field

jhipster-elastic

We have an Entity with an Enum field. This enum field gets saved in the database as string.

enum

public enum TipoContrato {
    ADJUDICACION_DIRECTA,  INVITACION_RESTRINGIDA,  LICITACION_PUBLICA
}

entity

@Entity
@Table(name = "contrato")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Document(indexName = "contrato")
public class Contrato implements Serializable {

    ...

    @Enumerated(EnumType.STRING)
    @Column(name = "tipo")
    private TipoContrato tipo;

the thing is, it includes underscore. Elasticsearch by default uses the standard analyzer which stores it and searches for it as one word. So, i.e. searching for ‘directa’ would not produce any results, but searching for ‘adjudicacion_directa’ would.

First you should enable http access to elasticsearch on your jhipster project to test if you’re getting results with a simple request, i.e using curl

curl -XPOST 'localhost:9200/contrato/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool" : {
      "should" : [
        { "match" : { "tipo" : "directa" } }
      ]
    }             
  }                                         
}
'

to get the results we’re expecting we need to change the analyzer that’s being used to index the field. We do this by changing the mapping. You could change it by using the REST api, and then reindexing your data. In development, we can reconfigure our mapping and then just do a mvn clean to regenerate it all.

Let’s change the mapping in our entity (we just need to add an annotation).

    @Enumerated(EnumType.STRING)
    @Column(name = "tipo")
    @Field(type = FieldType.String, analyzer = "simple")
    private TipoContrato tipo;

now, the results that came back from the curl request were fine. But the results from the search box didn’t. We had to include a custom search for it to work, cause the more general search doesn’t do a full text search apparently i.e. if you open in a new tab the url http://localhost:9200/contrato/_search?q=directa it doesn’t work but if you include the field it works http://localhost:9200/contrato/_search?q=tipo:directa

public interface ContratoSearchRepository extends ElasticsearchRepository<Contrato, Long> {

    @Query("{\n" +
        "  \"query\": {\n" +
        "    \"bool\" : {\n" +
        "      \"should\" : [\n" +
        "        { \"match\" : { \"_all\" : \"?0\" } },\n" +
        "        { \"match\" : { \"tipo\" : \"?0\" } }\n" +
        "      ]\n" +
        "    }             \n" +
        "  }                                         \n" +
        "}")
    Page<Contrato> buscarContrato(String query, Pageable page);

}

sources:
http://www.baeldung.com/spring-data-elasticsearch-tutorial
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
https://stackoverflow.com/questions/31837546/elasticsearch-splitting-words-on-underscore-search-founds-nothing
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
https://docs.spring.io/spring-data/elasticsearch/docs/3.0.2.RELEASE/api/

netbeans – Error package javax.validation.constraints does not exist

I’ve just upgraded to netbeans 8.2. I imported my previous settings from 8.1 when it asked me to do so. When I opened a previous project it throw me an error at compilation time complaining that I have missing the package javax.validation, at first I thought I had to reconfigure the jdk default home for netbeans (file [netbeans_dir]/etc/netbeans.conf) since it was choosing my os default open-jdk. But it had nothing to do with this.

You need to install the EJB and EAR plugin for it to work properly.

If you happened to have the plugin installed and still got the error above, it could be the application server you are using. In my case i configured the project to run on payara 173 which renamed the file bean-validator.jar to validation-api.jar to follow conventions, but netbeans 8.2 didn’t update the library path. A quick solution is to copy the file validation-api.jar in {payara-home}/glassfish/modules and name it bean-validator.jar.

sources:
https://groups.google.com/forum/#!topic/payara-forum/GVzQk2twHGM

Java 8 – Convert collection of objects to another type with streams

I was trying to simplify the following lines with java 8

this.audiencias = new ArrayList<>();
        for(Audiencia audiencia: carpeta.getAudiencias()){
            this.audiencias.add(new AudienciaDTO(audiencia));
        }

to this

this.audiencias = carpeta.getAudiencias.stream().map(AudienciaDTO::new).collect(Collectors.toList());

although it seems correct, and no error was thrown, it was returning an empty list.

As it happens, the list type is important. The attribute this.audiencias is of type Collection and carpeta.getAudiencias returns the same type, but the method stream() apparently only works with List types.

If I change the code to

List<Audiencia> asList = new ArrayList(carpeta.getAudiencias());
        this.audiencias = asList.stream().map(AudienciaDTO::new).collect(Collectors.toList());

it works as expected, but it is cumbersome the need to convert the Collection to a List type first.

for a one liner it would be

this.audiencias = new ArrayList<>(carpeta.getAudiencias()).stream().map(AudienciaDTO::new).collect(Collectors.toList());

fuentes:
http://tutorials.jenkov.com/java-collections/streams.html
https://docs.oracle.com/javase/tutorial/collections/interfaces/index.html