Skip to content

devon4j adding custom functionality

Cristian edited this page Jan 23, 2019 · 20 revisions

devon4j adding custom functionality

In the previous chapter we have seen that using Cobigen we can generate in a few clicks all the structure and functionality of an devon4j component.

In this chapter we are going to show how to add custom functionalities in our projects that are out of the scope of the code that Cobigen is able to cover.

Return the Access Code

The Jump the Queue design defines a User Story in which a visitor can register into an event and obtain an access code to avoid a queue.

In our standard implementation of the Jump the queue app we have used Cobigen to generate the components, so we have a default implementation of the services. Since AccessCode component is more complex and needs the use of CTOs we need to create our own usecasemanage and the methods save and delete.

Creating the usecasemanage

In the api(jtqj-api) inside the package accesscodemanagement/logic/api/usecase/ we are going to create a new interface called UcManageAccessCode, where we will define the save and delete methods.

import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
import com.devonfw.application.jtqj.queuemanagement.logic.api.to.QueueEto;

public interface UcManageAccessCode {

	/**
	 * Deletes a accessCode from the database by its id 'accessCodeId'.
	 * Decreases the count of customers of the queue assigned to the access
	 * code by one
	 *
	 * @param queueId Id of the queue to delete
	 * @return boolean <code>true</code> if the queue can be deleted,
	 *         <code>false</code> otherwise
	 */
	Boolean deleteAccessCode(long accessCodeId);

	/**
	 * Saves a queue and store it in the database.
	 * Increases the count of customers of the queue assigned to the access
	 * code by one
	 *
	 * @param queue the {@link QueueEto} to create.
	 * @return the new {@link QueueEto} that has been saved with ID and version.
	 */
	AccessCodeEto saveAccessCode(AccessCodeEto accessCode);

}

Then in the core(jtqj-core) inside the package accesscodemanagement/logic/impl/usecase we are going to create a class called UcManageAccessCodeImpl implementing the definition we just made and extending AbstractAccessCodeUc, this will allow us to have access to the repository. Also, Here is the part where we will add the needed extra features (in this case we are going to add/remove a customer when saveing/removing an accesscode)

import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Objects;

import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import com.devonfw.application.jtqj.accesscodemanagement.dataaccess.api.AccessCodeEntity;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeCto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeSearchCriteriaTo;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.base.usecase.AbstractAccessCodeUc;
import com.devonfw.application.jtqj.queuemanagement.dataaccess.api.QueueEntity;
import com.devonfw.application.jtqj.queuemanagement.logic.api.Queuemanagement;
import com.devonfw.application.jtqj.queuemanagement.logic.api.to.QueueEto;
import com.devonfw.application.jtqj.queuemanagement.logic.impl.usecase.UcManageQueueImpl;

@Named
@Validated
@Transactional
public class UcManageAccessCodeImpl extends AbstractAccessCodeUc implements UcManageAccessCode {

	@Inject
	private Queuemanagement queuemanagement;

	@Inject
	private UcFindAccessCode ucFindAccessCode;

	/** Logger instance. */
	private static final Logger LOG = LoggerFactory.getLogger(UcManageQueueImpl.class);

	@Override
	public Boolean deleteAccessCode(long accessCodeId) {

		// we get the cto throught the id passed
		AccessCodeCto accessCodeCto = getUcFindAccessCode().findAccessCodeCto(accessCodeId);

		// We get the queue, remove one customer and update it
		QueueEto queueEto = accessCodeCto.getQueue();
		int customerRemove = queueEto.getCustomers() - 1;
		queueEto.setCustomers(customerRemove);
		getQueuemanagement().saveQueue(queueEto);

		LOG.debug("The queue with id '{}' has decreased its customers to: {}.", queueEto.getId(),
				queueEto.getCustomers());

		// we delete the accesscode
		AccessCodeEntity accessCodeEntity = getBeanMapper().map(accessCodeCto.getAccessCode(), AccessCodeEntity.class);
		getAccessCodeRepository().delete(accessCodeEntity);
		LOG.debug("The accesscode with id '{}' has been deleted.", accessCodeId);

		return true;
	}

	@Override
	public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {
		// We make sure the object is not null
		Objects.requireNonNull(accessCodeEto, "UcManageAccessImpl accessCode null");

		AccessCodeEntity accessCodeEntity = getBeanMapper().map(accessCodeEto, AccessCodeEntity.class);

		long queueEntityId = accessCodeEntity.getQueueId();
		AccessCodeSearchCriteriaTo accessCodeSearchCriteriaTo = new AccessCodeSearchCriteriaTo();
		accessCodeSearchCriteriaTo.setQueueId(queueEntityId);
		Pageable pageable = PageRequest.of(0, 1000);
		accessCodeSearchCriteriaTo.setPageable(pageable);

		// we get the list of ctos that are in the same queue than our eto
		List<AccessCodeCto> accessCodeCtosInQueue = ucFindAccessCode.findAccessCodeCtos(accessCodeSearchCriteriaTo)
				.getContent();

		// if theres no ctos we set the ticket to the first code
		// else we get the digit of the last ticket in the list and generate a new code
		// for the ticket
		if (accessCodeCtosInQueue.isEmpty()) {
			accessCodeEntity.setTicketNumber("Q000");
		} else {
			AccessCodeEto lastAccessCode = accessCodeCtosInQueue.get(accessCodeCtosInQueue.size() - 1).getAccessCode();
			int lastTicketDigit = Integer.parseInt(lastAccessCode.getTicketNumber().substring(1));
			accessCodeEntity.setTicketNumber(generateTicketCode(lastTicketDigit));
		}
		// we set the creation time, startTime and endTime
		accessCodeEntity.setCreationTime(Timestamp.from(Instant.now()));
		accessCodeEntity.setStartTime(null);
		accessCodeEntity.setEndTime(null);
		// save the AccessCode
		AccessCodeEntity accessCodeEntitySaved = getAccessCodeRepository().save(accessCodeEntity);
		LOG.debug("The accesscode with id '{}' has been saved.", accessCodeEntitySaved.getId());
		QueueEntity queueEntity = getBeanMapper().map(getQueuemanagement().findQueue(queueEntityId), QueueEntity.class);

		// we add one customer to the queue and update it
		int customerAdd = queueEntity.getCustomers() + 1;
		queueEntity.setCustomers(customerAdd);
		QueueEto queueEtoSaved = getQueuemanagement().saveQueue(getBeanMapper().map(queueEntity, QueueEto.class));

		LOG.debug("The queue with id '{}' has increased its customers to: {}.", queueEtoSaved.getId(),
				queueEtoSaved.getCustomers());

		return getBeanMapper().map(accessCodeEntity, AccessCodeEto.class);
	}

	/**
	 * Generates a new ticked code using the ticket digit of the last codeaccess
	 * created
	 *
	 * @param lastTicketDigit the int of the last codeaccess created
	 * @return the String with the new ticket code (example: "Q005");
	 */
	public String generateTicketCode(int lastTicketDigit) {
		int newTicketDigit = lastTicketDigit + 1;
		String newTicketCode = "";
		if (newTicketDigit == 1000) {
			newTicketCode = "Q000";
		} else {
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append(newTicketDigit);
			while (stringBuilder.length() < 3) {
				stringBuilder.insert(0, "0");
			}
			stringBuilder.insert(0, "Q");
			newTicketCode = stringBuilder.toString();
		}
		return newTicketCode;
	}

	public Queuemanagement getQueuemanagement() {
		return this.queuemanagement;
	}

	public UcFindAccessCode getUcFindAccessCode() {
		return this.ucFindAccessCode;
	}
}

Finally, since we are injecting the class UcFindAccessCodeImpl.java both in the AccescodemanagementImpl.java and in the UcManageAccessCodeImpl.java then we need to add a tag (@Primary) to the class accesscodemanagement/logic/impl/usecase/UcFindAccessCodeImpl in order to avoid having bean injection problems:

@Named
@Validated
@Transactional
@Primary
public class UcFindAccessCodeImpl extends AbstractAccessCodeUc implements UcFindAccessCode {
Note
In the future this solution to the triangle problem between usecases and the management class may change

Adding to the logic

Inside jtqj-api and in the class accesscodemanagement/logic/api/AccessCodemanagement we are going to extend the UcManageAccessCode that we just defined

import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;

/**
 * Interface for Accesscodemanagement component.
 */
public interface Accesscodemanagement extends UcFindAccessCode,UcManageAccessCode {

}

After that, on the jtqj-core in the class accesscodemanagement/logic/impl/AccesscodemanagementImpl we will see that an error has appeared because the methods from the interface we just extended are missing. We add the unimplemented methods and inject the usecasemanage.

import javax.inject.Inject;
import javax.inject.Named;

import org.springframework.data.domain.Page;

import com.devonfw.application.jtqj.accesscodemanagement.logic.api.Accesscodemanagement;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeCto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeSearchCriteriaTo;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;
import com.devonfw.application.jtqj.general.logic.base.AbstractComponentFacade;

/**
 * Implementation of component interface of accesscodemanagement
 */
@Named
public class AccesscodemanagementImpl extends AbstractComponentFacade implements Accesscodemanagement {

	@Inject
	private UcFindAccessCode ucFindAccessCode;

	@Inject
	private UcManageAccessCode ucManageAccessCode;

	@Override
	public AccessCodeCto findAccessCodeCto(long id) {

		return ucFindAccessCode.findAccessCodeCto(id);
	}

	@Override
	public Page<AccessCodeCto> findAccessCodeCtos(AccessCodeSearchCriteriaTo criteria) {

		return ucFindAccessCode.findAccessCodeCtos(criteria);
	}

	@Override
	public Boolean deleteAccessCode(long accessCodeId) {

		return ucManageAccessCode.deleteAccessCode(accessCodeId);
	}

	@Override
	public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {

		return ucManageAccessCode.saveAccessCode(accessCodeEto);
	}
}

Adding to the service

To add the new service we need to add the definition to the accesscodemanagement/service/api/rest/AccesscodemanagementRestService.java. We are going to create a new /acessCode REST resource bound two methods, one called saveAccessCode and the other one called deleteAccessCode.

	/**
	 * Delegates to {@link Accesscodemanagement#saveAccessCode}.
	 *
	 * @param queue the {@link AccessCodeEto} to be saved
	 * @return the recently created {@link AccessCodeEto}
	 */

	@POST
	@Path("/accesscode/")
	public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto);

	/**
	 * Delegates to {@link Accesscodemanagement#deleteAccessCode}.
	 *
	 * @param id ID of the {@link AccessCodeEto} to be deleted
	 */
	@DELETE
	@Path("/accesscode/{id}/")
	public void deleteAccessCode(@PathParam("id") long id);

Then we need to implement the new methods in accesscodemanagement/service/impl/rest/AccesscodemanagementRestServiceImpl.java class.

	@Override
	public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {
		return this.accesscodemanagement.saveAccessCode(accessCodeEto);
	}

	@Override
	public void deleteAccessCode(long id) {
		this.accesscodemanagement.deleteAccessCode(id);
	}

Testing the changes Now run again the app with Eclipse and with postman call our new save service (POST) http://localhost:8081/jumpthequeue/services/rest/accesscodemanagement/v1/accesscode/ providing in the body a AccessCode object with the parameters needed:

{
	"queueId":"1",
	"visitorId":"1000000"
}

The result should be something similar to this:

jumpthequeue accesscode

In order to know if the new codeaccess has been succesfully created we can search all the ctos like we did in anterior steps, the new accesscode should be on the bottom:

jumpthequeue accesscode

To test the delete, you can send a delete to this url http://localhost:8081/jumpthequeue/services/rest/accesscodemanagement/v1/accesscode/{id} using the id found either on the save or on the search.

In this chapter we have seen how easy is extend a devon4j application, with few steps you can add new services to your backend app to fit the functional requirements of your projects or edit them to adapt the default implementation to your needs.

In the next chapter we will show how easy is to add validations for the data that we receive from the client.

Clone this wiki locally