JHipster – Making things a little less hip

Just like a good old Belgian beer can make for a nice change of pace after you’ve filled up on all those crafty IPAs and Stouts, it’s not always necessary to go for the latest and greatest. Last post saw us using Kafka as a message broker. In this blog post we’ll put a more traditional broker in between our thirsty beer clients and our brewery pumping out the happy juice! This blog is all about RabbitMQ! So let’s end this introduction and get started!
rabbitmq_logo
The final version of the code can be found here. Instead of building the whole thing from scratch like we did in the Kafka blog, we’ll be using a JHipster generator module this time.

JHipster Spring Cloud Stream generator

The JHipster Spring Cloud Stream generator can add RabbitMQ/Spring Cloud Stream support to our HelloBeer application. It uses the Yeoman Generator to do this.

Installation

Installation and running the generator is pretty straightforward. The steps are explained in the page’s README.md:

  • First install the generator
yarn global add generator-jhipster-spring-cloud-stream
  • Next run the generator (from the directory of our JHipster application) and accept the defaults
yo jhipster-spring-cloud-stream
  • Finally spin up the generated RabbitMQ docker file to start the
    RabbitMQ message broker
docker-compose -f src/main/docker/rabbitmq.yml up -d

Generated components

You can actually run the application now and see the queue in action. But before we do that let’s first take a look at what the generator did to our JHipster application:

  • application-dev.yml/application-prod.yml: modified to add RabbitMQ topic configuration;
  • pom.xml: modified to add the Spring Cloud Stream dependencies;
  • rabbitmq.yml: the docker file to spin up the RabbitMQ broker;
  • CloudMessagingConfiguration: configures a RabbitMQ ConnectionFactory;
  • JhiMessage: domain class to represent a message (with a title and a body) to be put on the RabbitMQ topic;
  • MessageResource: REST controller to POST a message onto the RabbitMQ topic and GET the list of posted messages;
  • MessageSink: Service class subscribes to the topic and puts received message in a List variable (the variable that gets read when issuing a GET via the MessageResource).

Running and testing

Alright, let’s test the RabbitMQ broker the generator set up for us. Run the JHipster application, login as admin user and go to the API page. You’ll see that a new message-resource REST service has been added to the list of services:

Screenshot from 2018-06-16 21-35-14

Call the POST operation a few times to post some messages to the RabbitMQ topic (which fills up the jhiMessages list):

Screenshot from 2018-06-16 21-37-48

Now, issue the GET operation to retrieve all the messages you POSTed in the previous step:

Screenshot from 2018-06-16 21-56-04

Cool! Working as expected. Now let’s get to work to put another RabbitMQ topic in place to decouple our OrderService (like we did with Kafka in our previous blog) again.

Replacing Kafka with RabbitMQ

rabbit-binder

Now we’re gonna put another RabbitMQ topic in between the Order REST service and the Order Service, just like we did with Kafka in our previous blogpost. Let’s leave the topic that the generator created in place. Since that guy is using the default channels, we’ll have to add some custom channels for our new topic that will handle the order processing.

First add a channel for publishing to a new RabbitMQ topic – we’ll be configuring the topic in a later step – and call it orderProducer:

public interface OrderProducerChannel {
  String CHANNEL = "orderProducer";

  @Output
  MessageChannel orderProducer();
}

We also need a channel for consuming orders for our topic. Let’s call that one orderConsumer:

public interface OrderConsumerChannel {
  String CHANNEL = "orderConsumer";

  @Input
  SubscribableChannel orderConsumer();
}

Now link those two channels to a new topic called topic-order in the application-dev.yml configuration file:

spring:
    cloud:
        stream:
            default:
                contentType: application/json
            bindings:
                input:
                    destination: topic-jhipster
                output:
                    destination: topic-jhipster
                orderConsumer:
                    destination: topic-order
                orderProducer:
                    destination: topic-order

The changes needed to be made in the OrderResource controller are similar to the changes we made for the Kafka setup. The biggest difference is in the channel names, since the default channels are already taken by the generated example code.
Another difference is that we put the EnableBinding annotation directly on this class instead of on a Configuration class. This way the Spring DI Framework can figure out that the injected MessageChannel should be of type orderProducer. If you put the EnableBinding on the Configuration class – like we did in our Kafka setup – you need to use Qualifiers or inject the interface – OrderProducerChannel – instead, else Spring won’t know what Bean to inject, since there are more MessageChannel Beans now.

@RestController
@RequestMapping("/api/order")
@EnableBinding(OrderProducerChannel.class)
public class OrderResource {

  private final Logger log = LoggerFactory.getLogger(OrderResource.class);
  private static final String ENTITY_NAME = "order";
  private MessageChannel orderProducer;

  public OrderResource (final MessageChannel orderProducer) {
    this.orderProducer = orderProducer;
  }

  @PostMapping("/process-order")
  @Timed
  public ResponseEntity<OrderDTO> processOrder(@Valid @RequestBody OrderDTO order) {
    log.debug("REST request to process Order : {}", order);
    if (order.getOrderId() == null) {
        throw new InvalidOrderException("Invalid order", ENTITY_NAME, "invalidorder");
    }
    orderProducer.send(MessageBuilder.withPayload(order).build());

    return ResponseEntity.ok(order);
  }
}

Again in our OrderService we also added the EnableBinding annotation. And again we use the StreamListener annotation to consume orders from the topic but this time we direct the listener to our custom orderConsumer channel:

@Service
@Transactional
@EnableBinding(OrderConsumerChannel.class)
public class OrderService {
  ....
  @StreamListener(OrderConsumerChannel.CHANNEL)
  public void registerOrder(OrderDTO order) throws InvalidOrderException {
    ....
  }
  ....
}

Building unit/integration tests for the RabbitMQ setup is not much different from the techniques we’ve used in the Kafka setup. Check my previous blog post for the examples.

Testing the setup

Alright, let’s test our beast again. These are the stock levels before:

Screenshot-2018-6-17 Item Stock Levels

Now let’s call the OrderResource and place an order of 20 Small bottles of Dutch Pilsner:

Screenshot from 2018-06-17 20-42-48

Check the stock levels again:

Screenshot-2018-6-17 Item Stock Levels(1)

Notice the new item stock level line! The inventory item went down from 90 to 70. Our RabbitMQ setup is working! Cheers!

Summary

In this blog post we saw how easy it is to switch from Kafka to RabbitMQ. The Spring Cloud Stream code mostly abstracts away the differences and didn’t change much. We also used a generator this time to do most of the heavy lifting. Time for a little vacation in which I’m gonna think about my next blog post. Again JHipster, check Spring Cloud Stream’s error handling possibilities or should I switch to some posts about other Spring Cloud modules? Let’s drink a few HelloBeerTM crafts and ponder about that!

References

Advertisements

JHipster – Adding some service

In our last blog post we focused on the Angular side of the generated application. This blog post is all about the Spring Boot server side part. In this post we’ll be adding some service to our HelloBeerTM app.
We’ll be developing on the app we’ve built in our previous JHipster blogs. Code can be found here.

But first let’s take a look at what’s in the server side part of our JHipster app.

Spring Boot architecture

05 - Spring boot architecture

All the entities we’ve added to our domain model will be exposed via REST operations. JHipster generates a layered architecture that corresponds to the hamburger in the picture.

The domain (or entity) object will be placed in the domain package. The corresponding repository will serve as the DAO and is placed in the repository package. Now if you’ve stuck to the defaults during generation, like I did, there will be no service and DTO layer for your entities (you can override this during generation). JHipster’s makers have the philosophy of omitting redundant layers. The service (and DTO) layers should be used for building complex (or composite) services that – for example – combine multiple repositories. The REST controllers by default just expose the domain objects directly and are placed in the web.rest package. JHipster calls them resources.

JHipster – Adding an OrderService

Eventually we wanna push our HelloBeer enterprise to the next level and start selling beers over the internet. So we need a REST service our customers can use for placing orders.

Order Service

So let us begin by adding an Order Service. The JHipster CLI has a command for this:

jhipster spring-service Order

Accept the defaults and in no time JHipster has generated the skeleton for our new OrderService:

package nl.whitehorses.hellobeer.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class OrderService {

    private final Logger log = LoggerFactory.getLogger(OrderService.class);

}

Order DTO

We also need an OrderDTO object to capture the JSON supplied to the exposed REST controller (which we’ll build in one of the next steps). Let’s keep it simple for now. Our OrderDTO contains an order reference (order id), a reference to our customer (customerId), and a list of inventory items (inventoryItemId and quantity) that the customer wants to order.

public class OrderDTO {
  private Long orderId;
  private Long customerId;
  private List<OrderItemDTO> orderItems;
  ....
}

public class OrderItemDTO {
  private Long inventoryItemId;
  private Long quantity;
  ..
}

Order Service implementation

For our OrderService implementation, we’re just gonna add a few checks making sure our inventory levels won’t run in the negative. If all checks are passed, our item stock levels are updated (i.e. new levels with new stock dates are inserted) according to the order.

@Service
@Transactional
public class OrderService {

  private final ItemStockLevelRepository itemStockLevelRepository;
  private final InventoryItemRepository inventoryItemRepository;

  private static final String ENTITY_NAME = "order";

  private final Logger log = LoggerFactory.getLogger(OrderService.class);

  public OrderService(ItemStockLevelRepository itemStockLevelRepository, InventoryItemRepository inventoryItemRepository) {
    this.itemStockLevelRepository = itemStockLevelRepository;
    this.inventoryItemRepository = inventoryItemRepository;
  }

  public void registerOrder(OrderDTO order) throws InvalidOrderException {
    // Map to store new item stock levels
    List<ItemStockLevel> itemStockLevelList = new ArrayList<>();

    for (OrderItemDTO orderItem : order.getOrderItems()) {
      ItemStockLevel itemStockLevelNew = processOrderItem(orderItem.getInventoryItemId(), orderItem.getQuantity());
      itemStockLevelList.add(itemStockLevelNew);
    }

    itemStockLevelRepository.save(itemStockLevelList);
    log.debug("Order processed");
  }

  // validate order items before processing
  // - assuming there are no multiple entries for one inventory item in the order
  // - if one order item entry fails, the whole order fails.
  private ItemStockLevel processOrderItem(Long inventoryItemId, Long qtyOrdered) {

    final InventoryItem inventoryItem = inventoryItemRepository.findOne(inventoryItemId);
    if (inventoryItem == null) {
      throw new InvalidOrderException("Invalid order", ENTITY_NAME, "invalidorder");
    }

    // find item stock level
    final Optional<ItemStockLevel> itemStockLevel = itemStockLevelRepository.findTopByInventoryItemOrderByStockDateDesc(inventoryItem);
    if (!itemStockLevel.isPresent()) {
      throw new InvalidOrderException("Invalid order", ENTITY_NAME, "invalidorder");
    }

    // check if quantity available
    Long qtyCurrent = itemStockLevel.get().getQuantity();
    Long newqty = qtyCurrent - qtyOrdered;
    if (newqty < 0L) {
      throw new InvalidOrderException("Invalid order", ENTITY_NAME, "invalidorder");
    }

    // construct new item stock level
    ItemStockLevel itemStockLevelNew = new ItemStockLevel();
    itemStockLevelNew.setInventoryItem(inventoryItem);
    itemStockLevelNew.setQuantity(newqty);
    itemStockLevelNew.setStockDate(ZonedDateTime.now(ZoneId.systemDefault()));
    return itemStockLevelNew;
  }

}

The code hopefully speaks for itself. In a nutshell: for every order item we first get the inventory item belonging to the inventory item id and check if it exists. Next we get the current item stock level for the inventory item. For this we’ve had to add the findTopByInventoryItemOrderByStockDateDesc method to the ItemStockLevelRepository first:

@SuppressWarnings("unused")
@Repository
public interface ItemStockLevelRepository extends JpaRepository<ItemStockLevel, Long> {

  Optional<ItemStockLevel> findTopByInventoryItemOrderByStockDateDesc(InventoryItem inventoryItem);
}

This gets us the item stock level at the most recent stock date (note that we get the implementation for free thanks to Spring). If such a level exists, we deduct the quantity ordered from the current quantity and if the current quantity is sufficient, we construct a new item stock level entry. After all order items are processed without validation errors, we store the new set of stock levels.
Not shown here are the OrderServiceIntTest in the nl.whitehorses.hellobeer.service package to test the new service and the new InvalidOrderException. Please check the GitHub code for the details.

Order Controller

Now let us add the controller for the Order Service. The JHipster CLI also has a command for this one:

jhipster spring-controller Order

Just add one POST action called processOrder, and see this controller (and a corresponding test class) being generated:

package nl.whitehorses.hellobeer.web.rest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Order controller
 */
@RestController
@RequestMapping("/api/order")
public class OrderResource {

  private final Logger log = LoggerFactory.getLogger(OrderResource.class);

  /**
  * POST processOrder
  */
  @PostMapping("/process-order")
  public String processOrder() {
    return "processOrder";
  }

}

Order Controller implementation

Alright. Find the code for the implementation below. Not much going on in here. The OrderService will be doing the heavy lifting and the controller just delegates to it.
We let the POST method return the supplied Order object after processing along with a status 200 code:

@RestController
@RequestMapping("/api/order")
public class OrderResource {

  private final Logger log = LoggerFactory.getLogger(OrderResource.class);

  private static final String ENTITY_NAME = "order";

  private final OrderService orderService;

  public OrderResource (final OrderService orderService) {
    this.orderService = orderService;
  }

  /**
  * POST processOrder
  */
  @PostMapping("/process-order")
  @Timed
  public ResponseEntity<OrderDTO> processOrder(@Valid @RequestBody OrderDTO order) {
    log.debug("REST request to process Order : {}", order);
    if (order.getOrderId() == null) {
      throw new InvalidOrderException("Invalid order", ENTITY_NAME, "invalidorder");
    }
    orderService.registerOrder(order);

    return ResponseEntity.ok(order);
  }

}

I’ve also implemented the generated test class OrderResourceTest. I’ve renamed it from OrderResourceIntTest. Since I’m integration testing the OrderService, I can suffice by unit testing the controller and just mocking the OrderService dependency. See the GitHub code for the test class implementation.

Beer tasting

All right. All pieces are in place to test our new service. If you fire up the application again and check the API menu (admin login required!), you’ll see the Swagger definition of our new service being displayed quite nicely:

Screenshot-2018-5-12 API

You can test the APIs by using the API page, but I prefer to user Postman for testing. You can get the urls from the Swagger definition. Please note that all APIs are secured and you need to send a header parameter along with the request. The parameter (called Authorization) can be found in the CURL section of the definition (click the Try it out! button first to see it):

Screenshot from 2018-05-12 20-34-59

Note that this parameter will change every time you reboot the server part of the application.

Now let’s first check our initial item stock levels:

Screenshot from 2018-05-12 20-45-04

As you can see, there is a stock level for inventory item 1 of 100 and a stock level for inventory item 2 of 100.

Let’s order 5 item 1 items and 10 item 2 items and see what happens!

Screenshot from 2018-05-12 20-54-44

Looking good so far: a response with a 200 Status Code containing the Order we put in our request.

Now for the final check: call the item stock level GET again:

Screenshot from 2018-05-12 20-56-23

And there you have it: two new item stock level entries with quantities of 95 for inventory item 1 and 90 for inventory item 2.

For completeness, I’ll also show you what happens when you try to order to much of one inventory item:

Screenshot from 2018-05-12 21-12-25

Great! So our validation also kicks in when needed.

JHipster – Contract-First

Now what about API-First development? As it turns out, JHipster supports that as well. If you paid attention during setup, you will have noticed the API-First option you can select. I didn’t select it when I generated the hb-jhipster application, but if I did, I would have seen a familiar entry in the generated Maven pom:

<plugin>
  <!--
    Plugin that provides API-first development using swagger-codegen to
    generate Spring-MVC endpoint stubs at compile time from a swagger definition file
  -->
  <groupId>io.swagger</groupId>
  <artifactId>swagger-codegen-maven-plugin</artifactId>
  <version>${swagger-codegen-maven-plugin.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>generate</goal>
      </goals>
      <configuration>
        <inputSpec>${project.basedir}/src/main/resources/swagger/api.yml</inputSpec>
        <language>spring</language>
        <apiPackage>nl.whitehorses.hellobeer.web.api</apiPackage>
        <modelPackage>nl.whitehorses.hellobeer.web.api.model</modelPackage>
        <generateSupportingFiles>false</generateSupportingFiles>
        <configOptions>
          <interfaceOnly>true</interfaceOnly>
          <java8>true</java8>
        </configOptions>
      </configuration>
    </execution>
  </executions>
</plugin>

Yes that’s right: the swagger-codegen-maven-plugin. If you put your Swagger definition in a src/main/resources/swagger/api.yml file, you can generate the Spring Boot code according to the contract by running the following Maven command:

./mvnw generate-sources

See the JHipster documentation for more information.

Feel free to experiment, I’ve already done that in one of my previous blogs here.

Swagger codegenThe main difference between mine and the JHipster approach is that they’re using the interfaceOnly option, while I used the delegate option.
It’s a subtle difference and mainly a matter of taste. With the interfaceOnly option, you need to implement the API interface and build a controller class complete with annotations. With the delegateOnly option, the ApiController is generated for you and you only need to implement a simple interface. See the picture for the difference (the yellow objects are generated, the blue ones you need to implement yourself).

Summary

In this blog post we’ve explored the server layer of our JHipster app a bit. We used the CLI to generate a new Service and Controller for our HelloBeerTM application. It’s nice to see JHipster helping us as much as possible along the way.
Before exploring the glorious world of microservices and Spring Cloud, I’ll put out 1 last JHipster blog for now (I’ll probably come back to check out their microservice options). In that blog I’m gonna check out the Kafka integration option JHipster provides. So for now: happy drinking and stay tuned!

References

Tweaking the JHipster App – Show me you IDs please, ow wait, don’t!

Like every craft has to have a cool name, a flashy etiquette and needs to be poured into the right glass to please the hipster drinking it, the same applies to serving them a JHipster application: it’s all about presentation!
So let’s dig right into it and shave of some of ’em rough edges of our JHipster app. We’re building on the application we generated in the previous blog post. Code can be found here.

images

Presenting the relationships

Alright. One of the most annoying things that can happen when you’re in the middle of ordering a great craft on a warm Summer day, is some big dude demanding your ID right then and there and you discovering that you left the darn thing at home. So let us get rid of those IDs! Like the ones on the Beer page for example:
Screenshot-2018-4-2 Beers
Those brewery ids don’t mean squat to your average refined beer drinker, so let’s tackle them first. We wanna swap the displayed ids with the corresponding brewery names. But before diving into the code let us take a look at the generated components to see what we’re dealing with.

For every entity JHipster generates a folder into the webapp/app/entities folder. For the Beer entity, for example, we’ve got a beer subfolder. Within we find the beer.component.html that serves as our overview page. The beer-detail.component.html is what is displayed when you press View, the beer-dialog.component.html when you press Create or Edit and the beer-delete-dialog.component.html when you press Delete. They all have their corresponding TypeScript classes.

Screenshot from 2018-04-02 16-14-26

The beer.model.ts handles the model classes, the beer.service.ts and beer-popup.service.ts classes handle the REST calls to the lower Spring Boot layer, beer.route.ts tackles all routes regarding the Beer entity (think of menu items, bookmark urls, foreign key hyperlinks and the Create, View, Edit and Delete links). Everything is packed in a separate Angular module, i.e. beer.module.ts and index.ts just exports all typescript classes in the Beer entity folder upwards in the Angular hierarchy.

Alright. So for changing the overview page displaying our beers with brewery ids, the beer.component.html page is the guy we need. Since the relationship between Beer and Brewery is represented by the entire Brewery class (so not only by the Brewery id) in the Beer class, we have the name for the taking.

Let’s first change the BaseEntity interface (all entities in a relationship are derived from this one) and add an (optional) name to it. The BaseEntity interface is available in the src/main/webapp/shared/model folder.

export interface BaseEntity {
  // using type any to avoid methods complaining of invalid type
  id?: any;
  name?: any;
}

This change is mainly so the IDE won’t complain when we try to use the name property of a relationship somewhere in our html pages.

Now change the beer.component.html so it uses the name of the Brewery instead of the id. First change the table header (for sorting):

<th jhiSortBy="brewery.name">
  <span jhiTranslate="helloBeerApp.beer.brewery">Brewery</span>
  <span class="fa fa-sort"></span>
</th>

Now change the table body (for display):

<td>
  <div *ngIf="beer.brewery">
    <a [routerLink]="['../brewery', beer.brewery?.id ]" >{{beer.brewery?.name}}</a>
  </div>
</td>

Note that we didn’t change the link as it would break the navigation from Brewery to Brewery detail (those bold links are clickable) . That’s it. Refresh the Beer page and revel in the magic! Screenshot-2018-4-2 Beers(3) The view page (beer-detail.component.html) is even simpler, just replace the one line that is displaying the brewery:

<dd>
  <div *ngIf="beer.brewery">
    <a [routerLink]="['/brewery', beer.brewery?.id]">{{beer.brewery?.name}}</a>
  </div>
</dd>

And voilà the detail page is displaying brewery names now instead of useless ids: Screenshot-2018-4-2 Beers(4)

Creating and editing the relationships

The functionality for creating and editing entities are shared on the same html page (beer-dialog.component.html). We want to change the select item linking the beer to the brewery, so that it displays brewery names instead of ids: Screenshot-2018-4-2 Beers(1) This one couldn’t have been easier. Just head over to the div displaying the select item, keep the code that handles displaying/selecting the right relationship based on the brewery id and only change the displayed brewery id into the brewery name:

<div class="form-group">
  <label class="form-control-label" jhiTranslate="helloBeerApp.beer.brewery" for="field_brewery">Brewery</label>
  <select class="form-control" id="field_brewery" name="brewery" [(ngModel)]="beer.brewery" >
    <option [ngValue]="null"></option>
    <option [ngValue]="breweryOption.id === beer.brewery?.id ? beer.brewery : breweryOption" *ngFor="let breweryOption of breweries; trackBy: trackBreweryById">{{breweryOption.name}}</option>
  </select>
</div>

Check out the Edit page now: Screenshot-2018-4-2 Beers(5) See how the brewery name is being displayed instead of the id. How cool is that?!

Autosuggesting

Let’s take this one step further. The inventory item page is still displaying the id for the Beers: Screenshot-2018-4-2 Inventory Items We could change this, like we did in the previous steps and display a list of Beer names. But the list of Beer names could become huge, certainly bigger than the list of breweries. So what if we replaced this guy with an auto-complete item? Sounds great, doesn’t it?! But how do we do that? Enter PrimeNG. PrimeNG is a set of UI components for Angular applications.

Installation

First add the PrimeNG lib to your JHipster application

npm install primeng --save

Next, add the auto-complete component to the module where we’re gonna use it, i.e. inventory-item.module.ts:

...
import { AutoCompleteModule } from 'primeng/autocomplete';
...
@NgModule({
    imports: [
        AutoCompleteModule,
        ...
    ],
    ...
})

Typescript code

For this blog post, we’re just gonna filter the complete Beer list already retrieved by the REST call in the NgOnInit() method – another option would be to omit this initial retrieval and add a REST method that can handle a filter. Then, every time you make a change in the auto-complete item, an instant REST call is made retrieving a list based on the then present filter.

These are the changes needed for the inventory-item-dialog-component.ts:

export class InventoryItemDialogComponent implements OnInit {
...
  beers: Beer[];
  beerOptions: any[];
  ...
  search(event) {
    this.beerOptions = this.beers.filter((beer) => beer.name.startsWith(event.query));
  }
  ...
}

So basically we just add a method filtering the beers starting with the string matching our query. This method will be called every time the input in the auto-complete item changes and will update the selectable options accordingly.

HTML page

Now lets add the auto-complete item on the inventory-item-dialog.component.html page (overwrite the select item):

<p-autoComplete  id="field_beer" name="beer" [(ngModel)]="inventoryItem.beer"  [suggestions]="beerOptions" (completeMethod)="search($event)" field="name" placeholder="Beer"></p-autoComplete>

Check the PrimeNG manuals for more information. The most important piece is adding the field attribute so the auto-complete item can work with a Beer object.

Styling

When you test the JHipster app at this point, you’ll notice the auto-complete functionality actually working already, albeit that the styling looks horrible. Luckily you can get PrimeNG to play nicely along with JHipster’s styling – which is based on the popular Bootstrap CSS library. Just add a few lines to the vendor.css file:

@import '~bootstrap/dist/css/bootstrap.min.css';
@import '~font-awesome/css/font-awesome.css';
@import '~primeng/resources/primeng.css';
@import '~primeng/resources/themes/bootstrap/theme.css';

This is a major improvement. One last optimization is to expand the auto-complete item to a width of 100% just like all the other items on the dialog pages. Add these line to the global.css file:

.ui-autocomplete {
    width: 100%;
}
.ui-autocomplete-input {
    width: 100%;
}

Now, testing the inventory item Edit page you’ll see a nicely integrated auto-complete item: Screenshot-2018-4-11 Inventory Items In the overview and detail pages of the Inventory Item entity we’ll just make the same changes we made for the Beer pages, i.e. exchanging the displayed ids for names: Screenshot-2018-4-11 Inventory Items(1)

Calendar

Alright this beer’s on the house! As a small extra, we’ll add in a calendar item to beautify the item stock level page (and we’ll also change that ugly id). Screenshot-2018-4-16 Item Stock Levels As you can see the Stock Date field could use a good calendar to select the date time, and now we’re on it that ugly xml date presentation we could use without as well.

As we did for the auto-complete item, we’ll be using PrimeNG here again. PrimeNG supports a calendar item. It’s dependent on Angular’s animations module. So let’s first install that guy into our project:

npm install @angular/animations --save

And add the necessary imports to the item-stock-level.module.ts (the module where we’re gonna add the calendar item to).

...
import {CalendarModule} from 'primeng/calendar';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
...
@NgModule({
    imports: [
        CalendarModule,
        BrowserAnimationsModule,
        ...
    ],
    ...
})

Next add the PrimeNG calendar item itself (replace the input item representing the Stock Date) to the item-stock-level-dialog.component.html page:

<p-calendar id="field_stockDate" type="datetime-local" [showIcon]="true" name="stockDate" [(ngModel)]="itemStockLevel.stockDate" showTime="true" hourFormat="24" dateFormat="yy-mm-dd"></p-calendar>

Now, the date format used by JHipster isn’t compatible with this calendar item date format. So let’s change that. Format the stock date returned by the REST service in the item-stock-level-popup.service.ts to a format that the calender item understands, i.e. not the XML date format:

itemStockLevel.stockDate = this.datePipe
  .transform(itemStockLevel.stockDate, 'yyyy-MM-dd HH:mm');

And of course we also need to change the formatting of the stock date when we send it back to the back-end. Alter the item-stock-level.service.ts for this. Just comment out the formatting line (since where sending a plain javascript Date back):

/**
 * Convert a ItemStockLevel to a JSON which can be sent to the server.
 */
private convert(itemStockLevel: ItemStockLevel): ItemStockLevel {
    const copy: ItemStockLevel = Object.assign({}, itemStockLevel);

    // copy.stockDate = this.dateUtils.toDate(itemStockLevel.stockDate);
    return copy;
}

That’s it! Now look at the dialog page when editing an item stock level row. Looks pretty neat (I’ve also changed that id reference into an description reference (not visible in the picture), I’ll not explain it, you can look it up in the code):

Screenshot-2018-4-17 Item Stock Levels

Summary

In this fairly long blog post, we’ve tweaked the front-end of a generated JHipster application. We made quite a few changes to make the application a bit more presentable. We performed the following changes:

  • Changing relationships, replacing ids with meaningful strings;
  • Adding an auto-complete item;
  • Adding a calendar item.

For the last two step we used some PrimeNG components.

In the next blog post we’ll take a closer look at the server side of a JHipster application. So grab yourself a fine craft beer and stay tuned!

References

Getting the (J)Hipsters aboard

When I think of craft beers, I think of long beards and oiled mustaches. The biggest community of craft drinkers are definitely the hipsters and HelloBeerTM wants to add the contents of their shiny leather wallets to its bank account. So high time to get those hipsters on board and dive into JHipster.

JHipster is a generator tool able to generate web applications based on a Spring Boot back-end (exposed via REST) and an Angular front-end. In this first part of a series of blog posts – in which we’re gonna delve into JHipster to discover its nooks and crannies – we’ll generate our first (monolithic) JHipster application.

I’ll assume that you’ve got your JHipster development environment all set up (steps can be found here (choose Local installation with YARN)). I’m using the current latest version of JHipster, i.e. 4.14.1.

The GitHub code can be found here.

First contact

Let’s hit the ground running and generate the skeleton of our first JHipster application. We’ll put the project in the hb-jhipster directory and call it HelloBeer:

jhipster

The CLI (Command Line Interface) of JHipster will ask a lot of questions and demand answers to every one of them. Let’s choose the defaults for most of them, except for the package name (nl.whitehorses.hellobeer). We’ll also add support for an additional language (Dutch) besides English.

A couple of seconds after we answered the last question (10 seconds on my laptop), the application will be generated and can be started in two steps:

The back-end can be started with Maven by the following command:

./mvnw

The front-end can be fired up with the help of YARN:

yarn start

After both components have been started, the application greets us on localhost:9000:
Screenshot-2018-3-25 Welcome, Java Hipster
The application doesn’t have any business value yet, but it’s nice to see that with just a few steps we got a working application for free, with a homepage, login screen, user registration functionality, i18n and a most helpful Administration menu. That menu gives us insight into the inner workings of our JHipster app. It enables viewings of metrics, logs and it gives us a page displaying all the (Spring Boot) Swagger APIs available in the app (every REST service we’ll add to the application will be automatically added to this page):
Screenshot-2018-3-25 API

Domain model

Our beers aren’t exactly flowing yet. The  most important part is still missing: getting the domain model in place! There are two ways of doing this: JHipster UML and JDL Studio. I’ll use the latter, since it’s an online editor that’s very easy to use. Simply hop over to https://start.jhipster.tech/jdl-studio/ and you’re good to go!

We’ll use a slightly more complex domain model than the one we’ve used in my previous blog – which basically was just one domain object – albeit the most important one – Beer. In this blog I’ll use a few objects with relationships, just to get the most out of JHipster.

There will be a Beer object, a Brewery object, an InventoryItem object and an ItemStockLevel object. The domain model will allow us to keep track of our inventory of crafts – the types and stock – and will demonstrate how JHipster will generate its back- and front-end with multiple related tables involved.

Screenshot-2018-3-27 JDL-Studio

As you can see in the upper figure: you’ll get a nice diagram on the right for free when typing out the model on the left. This is the entire model in text form, which you get when you download it to your computer:

entity Beer {
  name String required
  type BeerType required
}

enum BeerType {
  LAGER, PILSNER, PALE_ALE, INDIA_PALE_ALE, PORTER, STOUT, OTHER
}

entity Brewery {
  name String required
  countryName String
}

entity InventoryItem {
  itemDescription String,
  serving ServingType required,
  amount Integer required,
  rating Integer
}

enum ServingType {
  CAN, BOTTLE
}

entity ItemStockLevel {
  stockDate ZonedDateTime required,
  quantity Long
}

relationship ManyToOne {
  Beer{brewery} to Brewery
}

relationship ManyToOne {
  InventoryItem{beer} to Beer
}

relationship ManyToOne {
  ItemStockLevel{inventoryItem} to InventoryItem
}

// Set pagination options
paginate Beer, InventoryItem with pagination
paginate Brewery with infinite-scroll

// dto * with mapstruct

// Set service options to all except few
// service all with serviceImpl

By default JHipster will generate Controller classes per Entity. These Controllers will delegate directly to the Repository classes exchanging the domain objects.
If you need more control you can uncomment the “dto * with mapstruct” and “service all with serviceImpl” lines. This will generate Service classes in between the Repository and Controller classes together with DTO classes. These Service classes will map the Entity objects (used by the Repository) to DTO objects (used by the Controller). So these DTO objects will be exposed to the front-end instead of the Entity objects, giving you a lesser coupled application.
For now lets stick to the architecture without the Service layer and leave those lines commented out.

jhipster import-jdl jhipster-jdl.jh

The CLI will ask you to override some files, choose yes for all of them (option a).

CRUD

When you fire up the application again, you’ll see that the Entities menu contains four additional entries (one for each domain object). Play round a bit. This is the Inventory Items page after entering some data:

Screenshot-2018-3-28 Inventory Items

And this is the Edit screen available when you click on the Edit button on the first row:

Screenshot-2018-3-28 Inventory Items(1)

As you can see, the required field are nicely validated (green when filled, red when empty), pagination is added and basically all the CRUD functionality is there. Pages are bookmarked as well. The above page eg. is available on the URL http://localhost:9000/#/inventory-item. Error and confirmation regions are displayed on the page when the data entered is invalid, resp. committed.

The biggest issue with the pages as-is after generation are those ugly ids you need to select to connect to a related domain object. Those same ids are displayed in the overview page. For example instead of the Beer Name (which is a Brand Pilsner) we’ll see a 1 displayed in the above screenshots.

Back-end

Don’t worry. There’s quite a few JHipster blog posts in the making and in those we will dissect quite a bit of all the objects that JHipster generates. And generate some more!
The thing I’d like to show you right now is how to test those Spring Boot REST services. We’ll need an endpoint and also a security header to test one of the services. Let’s check the Swagger page for the InventoryItem (login as an admin user). All the information to fire a GET request is available under the Try it Out button:

Screenshot from 2018-03-28 19-51-28

You need the Request URL and the Authorization header (the value unfortunately changes with every restart).  Here’s a test with Postman:

Screenshot from 2018-03-28 19-54-47

Front-end

The screens JHipster generates are functional albeit a bit crude. In the next blog post we’re gonna beautify ’em: getting rid of those ugly ids for one, use a calendar, add some auto-suggesting and who knows what else.

For now we’re gonna do some simple configuration, without changing any code: we’re gonna change the footer and beautify those list items a bit, so INDA_PALE_ALE will be displayed as Inda pale ale.

This is all part of the i18n functionality of JHipster. All the customized labels are available in JSON files in the src/main/webapp/i18n directories. Let’s change the beerType.json files (English and Dutch) :

Screenshot from 2018-03-28 20-08-34

The footer can be adjusted in the global.json file:

Screenshot from 2018-03-28 20-11-12

Now let’s fire up the application again, switch to the Dutch language and see the changes on the Beer page (check the footer and the Pilsner type):

Screenshot-2018-3-28 Beers(1)

Summary

That’s enough for now! In this first blogpost on JHipster we’ve seen how easy it is to get a basic CRUD application up and running. Most of the work goes into building the domain model and the rest of the heavy lifting is done by JHipster.

We’ve also done a little customization on the front-end. In our next blog post, we’re gonna look at how difficult or easy it is to spiff up the front-end a little more. So grab a beer and stay tuned!

References

Cloning a VirtualBox openSUSE image

I recently tried to clone an openSUSE VirtualBox image. I thought it would be as simple as just copying the virtual disk image (vdi) file and create a new virtual machine based on it. So that’s what i did. But when i tried to couple the new hard disk file to the virtual machine VirtualBox gave me the following error:

Apparently a new virtual disk image in VirtualBox needs a unique identifier key. So after some research i found out it’s possible to make a clone of a hard disk that has a different unique identifier. But unfortunately that’s not all of the story. You have to make some additional changes to the clone to make it all work. So here are the steps you need to follow to make a successful clone.

1. Clone the hard disk

To clone the hard disk open a terminal window and issue the following command:

VBoxManage clonevdi <original>.vdi <clone>.vdi

2. Create a new Virtual Machine

Now create a new virtual machine with basically the same settings as the original virtual machine and couple it to the new cloned virtual disk image. As this image has been given a new unique identifier, you should have no problem registering it now.

3. Alter the hard disk identifiers

Having cloned the virtual disk image, i thought i was ready to roll. But on startup i encountered the following problem:

Apparently part of the UUID of the virtual disk image is used to identify the hard disk on startup. We have to change these references to their appropriate new ids.

3a. Startup in rescue mode

To startup the openSUSE vm in rescue mode insert the iso file you used for installation in the CD Drive of the vm. Upon startup select the Rescue System option from the menu.

3b. Mount the hard disk

Login as root and mount the hard disk (on my system this was /dev/sda2, this could be different on your system) via the following command:

mount /dev/sda2 /mnt

3c. Alter the identifiers

Now first find out what the new identifier of the hard disks should be. Issue the following command

hdparm -i /dev/sda

Note the identifier called SerialNo. This is the one you need. On my system it was VBa79c17fb-f28bb7c1.
Now there are 2 files you need to alter. First edit the file /mnt/etc/fstab and alter all the identifiers between /dev/disk/by-id/ata-VBOX_HARDDISK_ and -partx with the new identifier.
Next make corresponding changes to the file /mnt/boot/grub/menu.lst.
After this you can reboot the system

shutdown now -r

4. Repair your network settings

If you made no typos, openSUSE should start up with no problems. There’s one piece of configuration to do though. The clone has messed up the network configuration. This can be easily repaired via the YaST GUI tool. Open it and select Network Devices > Network Settings. You should see 2 Ethernet Controllers. One of them is not configured. Configure this controller with default settings and delete the other one. Now your clone is ready for use.

Using a nested property model in Apache Wicket for money validation

For one of my recent projects i needed to make sure that money fields were correctly translated from screen to database and vice versa.

The money fields are implemented as BigDecimal properties in the JPA model. The default Wicket behavior on input is to allow both the “.” and “,” characters and according to the active Locale to ignore the one that doesn’t correspond to the decimal seperator. This means that when the active Locale is the Dutch one (which uses the comma as the decimal seperator) and a user accidently enters €12.35 as the amount. It gets stored as €1235. This is very dangerous behavior.

The way we wanted the application to behave is to validate the input according to the Session Local and to disallow the entering of the wrong decimal seperator. There are probably a couple of way to do this, but the one i used involved using a custom validator and a nested property model.

Nested property model

The nested property model nests a BigDecimal model into a String model. Here’s the code.

package org.example.view.common;

import java.math.BigDecimal;
import org.apache.wicket.model.IModel;
import org.example.util.BigDecimalParser;
import org.example.view.format.BigDecimalToMoneySessionFormat;

public class DefaultMoneyModel implements IModel {

  private static final long serialVersionUID = 1479000955482442517L;
  private final IModel mNestedModel;

  public DefaultMoneyModel(IModel nestedModel) {
    mNestedModel = nestedModel;
  }

  public String getObject() {
    BigDecimal value = mNestedModel.getObject();
    // convert BigDecimal to String
    return BigDecimalToMoneySessionFormat.format(value);
  }

  public void setObject(String object) {
    // convert String to Bigdecimal
    BigDecimal value = BigDecimalParser.parseBigDecimal(object, getSessionLocale());
    mNestedModel.setObject(value);
  }

  public void detach() {
    mNestedModel.detach();
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Custom validator

The custom validator i wrote first tries to parse the String to a BigDecimal just like Apache Wicket does and adds an extra check for the decimal separator.

package com.redwood.exchange.view.validator;

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.validator.AbstractValidator;
import org.example.util.BigDecimalParser;

public class BigDecimalMoneyValidator extends AbstractValidator {

  private static final long serialVersionUID = 5638024896256902410L;

  @Override
  protected void onValidate(IValidatable validatable) {
    try {
      // First check that a normal conversion works
      @SuppressWarnings("unused")
      BigDecimal bigDecimal = BigDecimalParser.parseBigDecimal(
        validatable.getValue(), getSessionLocale());

      // Now check if the only non digit character is the decimal sign
      // corresponding to the locale and that it appears only once.
      checkDecimalSeparator(validatable.getValue(), getSessionLocale());
    } catch (Exception e) {
      error(validatable);
    }
  }

  private void checkDecimalSeparator(String value, Locale sessionLocale) {
    char decimalSeperator = new DecimalFormatSymbols(sessionLocale)
      .getDecimalSeparator();

    Pattern p = Pattern
      .compile("^\\d+(\\" + decimalSeperator + "\\d{2})?$");
    Matcher m = p.matcher(value);
    if (!m.find()) {
      throw new NumberFormatException("Invalid price.");
    }
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Utility classes and resources

I need a parser class for parsing a String, i.e. user input, to a BigDecimal and a formatter class for presentation of the BigDecimal as a money field on the web page. Both should take the current Locale into account. Here’s the code for both classes.

package com.redwood.exchange.util;

import java.math.BigDecimal;
import java.util.Locale;
import org.apache.wicket.util.convert.converters.BigDecimalConverter;

public class BigDecimalParser {

  public static BigDecimal parseBigDecimal(String s, Locale locale) {
    BigDecimalConverter converter = new BigDecimalConverter();
    BigDecimal bigDecimal = (BigDecimal) converter.convertToObject(s,
      locale);
    return bigDecimal;
  }
}
package org.example.view.format;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class BigDecimalToMoneySessionFormat {

  public static String format(BigDecimal decimal, Locale locale) {
    if (decimal == null) {
      return "";
    }
    else {
      DecimalFormat decimalFormat = new DecimalFormat();
      decimalFormat.setMinimumFractionDigits(2);
      decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols
        .getInstance(locale));
      return decimalFormat.format(decimal);
    }
  }
}

Finally for displaying an error message i added a WicketApplication.properties file on the org.example.view level

BigDecimalMoneyValidator=The entered value is not a valid ${label}.

Adding it all to a Wicket Panel

Now that we have all the necessary code, let’s use it on a Wicket panel. Assume that we have a JPA entity class named Order with a property called price, which is a BigDecimal.

entering money

The money field is put on a html page corresponding to a Wicket panel via the following snippet:

  <span><div wicket:id="price" /></span>

The following code snippet from the corresponding Wicket Panel class will couple the component to the nested model and add the custom validator.

  TextField<String> priceField = new TextField<String>(
    "price", new DefaultMoneyModel(new PropertyModel<BigDecimal>(
    order, "price")));
  priceField.add(new BigDecimalMoneyValidator());

displaying money

The money field is put on a html page corresponding to a Wicket panel via the following snippet (in this case it’s put in a html table):

  <td wicket:id="price">EUR 12.95</td>

Now the following code in the corresponding Wicket panel class will couple the component to the nested model for use in a DataView.

  final DataView<Order> dataView = new DataView<Order>(..,..) {

    private static final long serialVersionUID = -6981777123077670308L;

    @Override
    public void populateItem(final Item<JPAEntity> item) {
      ..
      item.add(new Label("price", BigDecimalToMoneySessionFormat.format(item
        .getModelObject().getPrice())));
    }
  };

More info

For more info on using nested property models check out this excellent blog by Jeremy Tomerson.

Autosuggesting in Seam

Today I implemented an autosuggest textfield in a search page. Although technically not a Seam but a Richfaces feature, I baked it into a default Seam page. I added it to a default Seam search page backed up by an EntityQuery<E> subclass. Below you’ll find a screenshot of the Search Form containing the autosuggest field. It’s a search form for querying the collection of golfers in the database.

Autosuggest Search Form

Let’s take a walk through the most important code to get this example working.

Model classes

The golfer data in the database is contained in 2 tables MEMBER and GOLFER. These are mapped to 2 JPA classes called org.open18.model.Member and org.open18.model.Golfer respectively.

org.open18.model.Member

package org.open18.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "MEMBER", uniqueConstraints = { @UniqueConstraint(columnNames = "username") })
public abstract class Member implements Serializable {
	private Long id;
	private String userName;

	@Id
	@GeneratedValue
	public Long getId() {
		return id;
	}

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

	@Column(name = "username", nullable = false)
	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

org.open18.model.Golfer

package org.open18.model;

package org.open18.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.jboss.seam.annotations.Name;

@Entity
@PrimaryKeyJoinColumn(name = "MEMBER_ID")
@Table(name = "GOLFER")
@Name("golfer")
public class Golfer extends Member {
	private String firstName;
	private String lastName;
	private Date dateJoined;

	@Column(name = "last_name", nullable = false)
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Column(name = "first_name", nullable = false)
	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@Transient
	public String getName() {
		return (firstName + ' ' + lastName);
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "joined", nullable = false, updatable = false)
	public Date getDateJoined() {
		return dateJoined;
	}

	public void setDateJoined(Date dateJoined) {
		this.dateJoined = dateJoined;
	}

}

Action class

The page is backed up by the action class org.open18.action.GolferList. This class contains an action method for providing the data for the autosuggest field.

org.open18.action.GolferList

package org.open18.action;

import java.util.Arrays;
import java.util.List;

import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.framework.EntityQuery;
import org.open18.model.Golfer;

@Name("golferList")
public class GolferList extends EntityQuery<Golfer> {
	private static final String EJBQL = "select golfer from Golfer golfer";

	private static final String[] RESTRICTIONS = { "lower(golfer.lastName) like lower(concat(#{golferList.golfer.lastName},'%'))", };

	private Golfer golfer = new Golfer();

	public GolferList() {
		setEjbql(EJBQL);
		setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS));
		setMaxResults(25);
	}

	public Golfer getGolfer() {
		return golfer;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public List<Golfer> autocomplete(Object suggest) {

		String pref = (String) suggest + "%";

		List<Golfer> golferSuggestList;
		golferSuggestList = getEntityManager()
				.createQuery(
						"select golfer from Golfer golfer where golfer.lastName like :golfer")
				.setParameter("golfer", pref).getResultList();

		return golferSuggestList;

	}
}

Page descriptor

The page descriptor contains page parameters for storing some search and navigation criteria.

golferList.page.xml

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">

  <param name="from" />

  <param name="lastName" value="#{golferList.golfer.lastName}" />

</page>

Page

Finally the page itself. After the code, I’ll explain some of the elements/attributes on the page.

GolferList.xhtml

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:s="http://jboss.com/products/seam/taglib"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:a="http://richfaces.org/a4j"
  xmlns:rich="http://richfaces.org/rich"
  template="layout/template.xhtml">

  <ui:define name="body">

    <h:form id="courseSearch" styleClass="edit">

      <rich:simpleTogglePanel label="Course Search Filter"
        switchType="ajax">

        <s:decorate template="layout/display.xhtml">
          <ui:define name="label">Last Name</ui:define>
          <h:inputText id="lastName"
            value="#{golferList.golfer.lastName}">
          </h:inputText>
          <rich:suggestionbox ajaxSingle="true" for="lastName"
            var="_golfer" minChars="2"
            suggestionAction="#{golferList.autocomplete}">
            <h:column>
              <h:outputText value="#{_golfer.lastName}" />
            </h:column>
            <a:support event="onselect" reRender="golferSearchResults" />
          </rich:suggestionbox>
        </s:decorate>

      </rich:simpleTogglePanel>

      <div class="actionButtons">
        <h:commandButton id="search"
          value="Search" action="/golferList.xhtml">
        </h:commandButton> <s:button
          id="reset" value="Reset" includePageParams="false" />
      </div>
    </h:form>

    <rich:panel id="golferSearchResults">
      <f:facet name="header">Golfers</f:facet>

      <div class="results">
        <h:outputText
          value="There are no golfers registered."
          rendered="#{empty golferList.resultList}" />
        <rich:dataTable
          id="golferList" var="_golfer" value="#{golferList.resultList}"
          rendered="#{not empty golferList.resultList}">
          <h:column>
            <f:facet name="header">Username</f:facet>
            <s:link id="view" value="#{_golfer.userName}"
              view="/profile.xhtml" propagation="none">
              <f:param name="golferId" value="#{_golfer.id}" />
            </s:link>
          </h:column>
          <h:column>
            <f:facet name="header">Name</f:facet>
            #{_golfer.name}
          </h:column>
          <h:column>
            <f:facet name="header">Date Joined</f:facet>
            <h:outputText value="#{_golfer.dateJoined}">
              <s:convertDateTime pattern="MMMM dd, yyyy" />
            </h:outputText>
          </h:column>
        </rich:dataTable>
      </div>

    </rich:panel>

  </ui:define>

</ui:composition>
  • layout/template.xhtml, layout/display.xhtml: these are standard facelets generated by seam-gen;
  • rich:suggestionbox: this element will take care of the suggestion data for the input textfield
  • for=”lastName”: points to the if of the h:inputText component for which the autosuggest data will be shown;
  • minChars=”2″: After 2 characters are entered the suggestion list will appear
  • suggestionAction=”#{golferList.autocomplete}”: Calls the autocomplete action method on the GolferList action class, which will return a list of golfers;
  • h:column: The result will be shown as one column and will contain a suggestion list of the golfers last names via the h:outputText component;
  • a:support: This element takes care of rerendering the golferSearchResults panel when a user has selected a last name in the suggestion list.
  • Performance note

    Hitting the database everytime a user is rumbling in the autosuggest field is probably not the best solution. As an alternative you could create a page scoped factory which captures the autosuggest data in a List<Golfer> variable. This factory could then be injected into the golferList component. In this case the autosuggest query will only be executed once per page visit. See the scriptlets below.

    components.xml

    	....
    	<framework:entity-query name="golferSuggestQuery"
    		ejbql="select golfer from Golfer golfer" >
    	</framework:entity-query>
    	<factory name="golferSuggest" value="#{golferSuggestQuery.resultList}" scope="page" />
    	....
    

    org.open18.action.GolferList

    @Name("golferList")
    public class GolferList extends EntityQuery<Golfer> {
    	@In(create=true,value="golferSuggest")
    	List<Golfer> golferSuggest;
    	....
    	public List<Golfer> autocomplete(Object suggest) {
    		String pref = (String) suggest;
    
    		List<Golfer> golferSuggestList = new ArrayList<Golfer>(0);
    
    		for (Golfer g : golferSuggest) {
    			if (g.getLastName().startsWith(pref)) {
    				golferSuggestList.add(g);
    			}
    		}
    
    		return golferSuggestList;
    	}
    	....
    }