jhipster – query the embedded elastic search instance

In jhipster you have the option to use elasticsearch, you know, for search. In development mode it includes an embedded instance but I couldn´t find anywhere how to run a query against it to test if it was working or not before coding it.

I tried to use the default port on localhost:9200 but I had no luck. As it happens, jhipster uses Spring Data Jest and its autoconfiguration. Then I searched for the class in charge of this, ElasticsearchJestAutoConfiguration. Looking at the code it selects a random port every time if one is not specified, so we just need to add the following property in our application-dev.yml file.

spring:
  data:
    elasticsearch:
      properties:
        http:
          port: 9200

and boom! we can use the Elastic Search REST API with its Query DSL to test our queries or use other endpoints like _analyze, _mappings and so forth.

ionic – file-chooser plugin not available on ionic devapp

I was trying to add the FileChooser plugin to an ionic app and test it out with the ionic devapp on an android device. It was throwing me the following error:

[ng] [console.log]: “Ionic Native: deviceready event fired after 1802 ms”
[ng] [console.warn]: “Native: tried calling FileChooser.open, but the FileChooser plugin is not installed.”
[ng] [console.warn]: “Install the FileChooser plugin: ‘ionic cordova plugin add cordova-plugin-filechooser'”
but I did add the plugin as per the documentation. Even tried removing and adding it again as some people suggested but didn’t work.

I finally discovered -sadly- that the filechooser plugin isn’t supported by the ionic devapp app, you need to test it out with a real device 🤷🏽‍♂️.

docker – serve different web apps from different paths through nginx

nginx-reverse-proxy

We have a couple of web apps -in different technologies- which are part of an application, i.e. they are related to each other.

We wanted to run them as a whole using docker-compose and routing (proxying) with nginx to access them at the same server but on different paths.

mydomain.com/admin
mydomain.com/reports

First we built our images we’re going to use in docker-compose and test them independently (docker build, docker run). Then we created the following docker-compose.yml

version: '3'
services:
    admin:
        image: mydomain.com/admin
        environment:
            - SPRING_PROFILES_ACTIVE=prod,swagger
        ports: 
            - 8080
    reports:
        image: mydomain.com/reports
        ports: 
            - 8080
    nginx:
        image: nginx
        ports: 
            - 80:80
            - 443:443
        links:
            - admin
            - reports
        volumes: 
            - ./nginx.conf:/etc/nginx/nginx.conf:ro

and the corresponding nginx.conf

events {
}

http {
  server {
    server_name localhost;

    location /reports/ {
      proxy_pass http://declaranet-reportes:8080/;
    }

    location /admin/ {
        proxy_pass http://admin:8080/webapp/;
        sub_filter '/webapp' '/admin';
        sub_filter_once off;
        sub_filter_types text/html text/xml text/javascript text/css;
        proxy_cookie_path '/webapp' '/admin';
    }
  } 
}

We add the events section since it’s required, then we named the server localhost (you may use your domain here).
Then we configure the routes. The ‘reports’ app didn’t required any additional configuration -it’s an angularjs app, but pay attention to the location and proxypass urls ending with a trailing slash, this is important!. The ‘admin’ app uses a Glassfish server behind the scenes and runs on a different context (path) than root (/) so we need to rewrite the path inside the html, css, js, etc. That’s exactly what the sub_filter is doing. Additionally we also moved the cookie path for authentication to work.

references:
https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
https://dev.to/domysee/setting-up-a-reverse-proxy-with-nginx-and-docker-compose-29jg
https://stackoverflow.com/questions/39514293/docker-nginx-proxy-how-to-route-traffic-to-different-container-using-path-and-n
Ver en Medium.com
https://stackoverflow.com/questions/32542282/how-do-i-rewrite-urls-in-a-proxy-response-in-nginx
http://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter
https://stackoverflow.com/questions/55163316/cookie-path-with-nginx-reverse-proxy
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cookie_path

jhipster – run jar/war as a service on ubuntu

There are instructions on how to run your jhipster app as a service in the documentation.

These are the steps and problems I encounter while configuring one of our apps to run as a service in Ubuntu 16.04.3 LTS. The app was generated with jhipster 4.14.3.

First we have to build our app.

mvn -Pprod,no-liquibase -DskipTests package

we don’t use liquibase and we skip tests. It should produce a war or jar file depending on the jhipster version. Either way it should be runnable. You may test it with java -jar app.war

now let’s copy it to the server

rsync -av app.war <user>@<server>:.

let’s connect to the server and configure the service. We’re going to create a specific user to run the service.

ssh <user>@<server>
sudo useradd -M app   # create user without home dir
sudo usermod -L app   # lock the user to prevent logging in
sudo usermod -aG app app   # create group and add user to it
sudo chattr +i app.war
sudo ln -s /full/path/to/app.war /etc/init.d/app
sudo systemctl daemon-reload

let’s start it to test

sudo service app start   # test that the app is working and then stop
sudo service app stop

alright, now let’s pass in required parameters or env variables stackoverflow
I’ve tried

sudo service declaranet-reportes start --server.port=8010
sudo RUN_ARGS="--server.port=8010" service declaranet-reportes start
sudo JAVA_OPTS="-Dserver.port=8010" service declaranet-reportes start
sudo JAVA_OPTS="-Drun.arguments=\"--server.port=8010\"" service declaranet-reportes start

but none of them worked. I had to create a conf file and put them inside it.

Let’s create the conf file (it should be named the same as your war file but with .conf extension instead)

nano app.conf   # make sure it's on the same folder as app.war
RUN_ARGS="--server.port=8010 --spring.datasource.username=dbuser --spring.datasource.password=dbpass"

test again that the app is working correctly and then let’s add it to the startup scripts to run when the server is restarted.

sudo update-rc.d app defaults

to list in which run levels it’s running

ls -l /etc/rc?.d/*app

those links starting with K is for Kill and S is for Start, so those starting with S indicate in which run levels is going to start.

sources:
https://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html
https://unix.stackexchange.com/questions/293875/failed-to-start-service
https://superuser.com/questions/77617/how-can-i-create-a-non-login-user
https://hostadvice.com/how-to/how-to-create-a-non-root-user-on-ubuntu-18-04-server/
https://stackoverflow.com/questions/40035831/passing-run-args-for-spring-boot-application-running-as-service-in-linux
https://stackoverflow.com/questions/40095464/multiple-parameters-not-getting-passed-when-i-start-my-spring-boot-application-i

angularjs – upload file with bootsrap button

Selection_001

We have an old project with angularjs 1.5.8 and bootstrap 3.3.7 in which we needed to add a button to allow the user to upload a CSV file.
The app was generated with jhipster 4.14.3

The first problem was, how to hide the input control and show a bootstrap button instead. stackoverflow

It really depends on the bootstrap version you are using, in our case we did this.

easy, done!

Now we need to bind this to an event in our controller in order to send the selected file to the server -which we will implement later-

There are several solutions to do this.
We didn’t want to add a library since the only requirement is to add the file (no progress indicators, no multiple files, etc.) and it’s an old app, so the less we add, the better.

We couldn’t use ng-change since you can’t use ng-model for an input of type ‘file’.
Some people suggested to use the plain onchange event like so.

html

<input type="file" onchange="angular.element(this).scope().fileSelected(this)" />

controller

$scope.fileSelected = function (element) {
    var myFileSelected = element.files[0];
};

but it would not work if you have debug disabled, which in most production apps would be.

We had to add a directive in order to bind our event to the controller as explained here.

Notice that we are using FormData and the $http service to POST our request to the server as explained here.

By setting ‘Content-Type’: undefined, the browser sets the Content-Type to multipart/form-data for us and fills in the correct boundary. Manually setting ‘Content-Type’: multipart/form-data will fail to fill in the boundary parameter of the request.

And finally we need to implement our endpoint to process the file in the server.

@PostMapping("/padron/import")
    @Transactional
    public ResponseEntity<Void> importPadron(MultipartFile filePadron, HttpServletResponse response) throws Exception {
        log.debug("REST request to import CSV data: {} ({} - {} bytes)",
            filePadron.getName(), filePadron.getContentType(), filePadron.getSize());
        // ... process your file here or throw Exception if not valid
        return ResponseEntity.ok().build();
    }

sources for CSV processing and file upload:
https://www.callicoder.com/spring-boot-file-upload-download-rest-api-example/
https://attacomsian.com/blog/export-download-data-csv-file-spring-boot
http://zetcode.com/articles/opencsv/

jhipster – about filtering entities

Filtering entities in jhipster is well explained in its documentation, the introduction states

After the basic CRUD functionalities are implemented for an entity, there is a very common request to create various filters for the attributes of the entity, so the server could be used more effectively. These filters should be sent as the request parameters, so any client – and any browser – could use it easily. Additionally, these filters should follow a sane, and concise pattern, and they must be allowed combining them freely.

The sane and concise pattern allows us to query our entities like so

GET /user?id.greaterThan=5&name.contains=jhon&status.in=active,verified

We have many filters available depending on the field type; equals, in, specified (whether is null or not), contains, greaterThan, lessThan, greaterOrEqualThan, lessOrEqualThan. And most important, we can combine them freely.

Jhipster achieved its goal by providing an abstract QueryService, Criteria interface and Filter models so you only need to extend them in your own Service and Criteria to be used by your Controller.

First you need to extend your Repository from JpaSpecificationExecutor

@Repository
public interface TitulosRepository extends JpaRepository<Titulo, Long>, JpaSpecificationExecutor<Titulo> {
}

then we need to create our own Criteria. Notice we have fields of correct filter type for each field in our domain object, in this case a Long and an Enumeration

package company.service.dto

import io.github.jhipster.service.Criteria;
import io.github.jhipster.service.filter.Filter;
import io.github.jhipster.service.filter.LongFilter;
import company.domain.enumeration.EstatusTitulo;

import java.io.Serializable;
import java.util.Objects;

public class TituloCriteria implements Serializable, Criteria {

    private static final long serialVersionUID = 1L;

    private LongFilter id;
    private EstatusTituloFilter estatus;

    public TituloCriteria() {
    }

    public TituloCriteria(TituloCriteria other) {
        this.id = other.id == null ? null : other.id.copy();
        this.estatus = other.estatus == null ? null : other.estatus.copy();
    }

    @Override
    public TituloCriteria copy() {
        return new TituloCriteria(this);
    }

    public LongFilter getId() {
        return id;
    }

    public void setId(LongFilter id) {
        this.id = id;
    }

    public EstatusTituloFilter getEstatus() {
        return estatus;
    }

    public void setEstatus(EstatusTituloFilter estatus) {
        this.estatus = estatus;
    }

    // ... Overrides equals, hashCode and toString

    public static class EstatusTituloFilter extends Filter<EstatusTitulo> {
        public EstatusTituloFilter() {
        }

        public EstatusTituloFilter(EstatusTituloFilter filter) {
            super(filter);
        }

        @Override
        public EstatusTituloFilter copy() {
            return new EstatusTituloFilter(this);
        }
    }
}

now let’s create our Service

package company.service;

import io.github.jhipster.service.QueryService;
import company.domain.Titulo;
import company.domain.Titulo_;
import company.repository.TitulosRepository;
import company.service.dto.TituloCriteria;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class TitulosQueryService extends QueryService<Titulo> {
    private final static Logger log = LoggerFactory.getLogger(TitulosQueryService.class);

    private final TitulosRepository titulosRepository;

    public TitulosQueryService(TitulosRepository titulosRepository) {
        this.titulosRepository = titulosRepository;
    }

    @Transactional(readOnly = true)
    public Page<Titulo> findByCriteria(TituloCriteria criteria, Pageable page) {
        log.debug("find by Criteria: {}, Page: {}", criteria, page);
        final Specification<Titulo> specification = createSpecification(criteria);
        return titulosRepository.findAll(specification, page);
    }

    private Specification<Titulo> createSpecification(TituloCriteria criteria) {
        Specification<Titulo> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), Titulo_.id));
            }
            if (criteria.getEstatus() != null) {
                specification = specification.and(buildSpecification(criteria.getEstatus(), Titulo_.estatus));
            }
        }
        return specification;
    }

}

finally in our @RestController

    @GetMapping("/titulos")
    public ResponseEntity<List<Titulo>> getAll(TituloCriteria criteria, Pageable pageable,
                                               @RequestParam MultiValueMap<String, String> queryParams,
                                               UriComponentsBuilder uriBuilder) {
        log.debug("REST request to get a page of Titulos by criteria: {}", criteria);
        Page<Titulo> page = titulosQueryService.findByCriteria(criteria, pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(uriBuilder.queryParams(queryParams), page);
        return ResponseEntity.ok().headers(headers).body(page.getContent());
    }

then you can query your REST api like

curl -X GET --header 'Accept: application/json' --header 'Authorization: Bearer ...' 'http://localhost:9000/api/titulos?estatus.in=EXPEDIDO,FIRMADO'

notice the estatus.in query parameter.

you may also use or combine other filters

curl -X GET --header 'Accept: application/json' --header 'Authorization: Bearer ...' 'http://localhost:9000/api/titulos?estatus.equals=EXPEDIDO,FIRMADO&amp;id.greaterThan=5'

when I had to add it in the client code I used the spread operator to include or not the filter in the following manner

loadAll() {
    this.loading = true;
    this.titulosService
      .query({
        page: this.page - 1,
        size: this.itemsPerPage,
        sort: this.sort(),
        ...(this.estatusFilter && { 'estatus.equals': this.estatusFilter })
      })
      .subscribe(
        (res: HttpResponse<ITitulo[]>) => this.paginateTitulos(res.body, res.headers),
        (res: HttpErrorResponse) => this.onError(res.message)
      );
  }

you may set the estatusFilter with a PrimeNG dropdown or select item.

<p-dropdown [(ngModel)]="estatusFilter" [options]="titulosEstatus"/>

sources:
https://www.jhipster.tech/entities-filtering/
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
https://stackoverflow.com/questions/48346341/spring-boot-jpa-implementing-search-queries-with-optional-ranged-criteria
https://www.baeldung.com/spring-data-criteria-queries
https://docs.jboss.org/hibernate/orm/current/topical/html_single/metamodelgen/MetamodelGenerator.html