diff --git a/doc/src/technical/howto-develop-an-use-case-in-navalplan.rst b/doc/src/technical/howto-develop-an-use-case-in-navalplan.rst index 3313e2031..9f7522cd4 100644 --- a/doc/src/technical/howto-develop-an-use-case-in-navalplan.rst +++ b/doc/src/technical/howto-develop-an-use-case-in-navalplan.rst @@ -12,9 +12,9 @@ How To Develop An Use Case In NavalPlan Commons Attribution-ShareAlike 3.0 licence, available in http://creativecommons.org/licenses/by-sa/3.0/. :Abstract: - The goal of this document is develop a CRUD_ (create, read, update and delete) - use case in NavalPlan_. Through carrying out this exercise you will see the - basic structure of NavalPlan and underlying technology stack. + The goal of this document is develop a complete CRUD_ (create, read, update + and delete) use case in NavalPlan_. Through carrying out this exercise you + will see the basic structure of NavalPlan and underlying technology stack. .. contents:: Table of Contents @@ -476,7 +476,7 @@ proper ``db.changelog-XXX.xml`` file:: referencesUniqueColumn="false"/> -As you can see this specify the different tables to be created on database and +As you can see, this specify the different tables to be created on database and also some constraints like foreign keys. Usually you can take a look to other Liquibase changes to know how to create a table or some field. Also a good idea is to check the result of your changeset against testing database (which is @@ -864,7 +864,7 @@ In the next paragraphs different parts of the file will be reviwed. model="@{controller.stretchesFunctionTemplates}" ``NewDataSortableGrid`` is a special component defined in NavalPlan, that -extends ``Grid`` component adding sorting feature for columns. As you can see +extends ``Grid`` component adding sorting feature for columns. As you can see, ``model`` attribute is set, which means that a method called ``getStretchesFunctionTemplates`` in controller will be called. Thise method will have the responsibility to communicate with model layer in order to get the @@ -1142,12 +1142,20 @@ folder: * ``IStretchesFunctionTemplateModel.java``:: + package org.navalplanner.web.planner.allocation.streches; + + ... + public interface IStretchesFunctionTemplateModel { ... } * ``StretchesFunctionTemplateModel.java``:: + package org.navalplanner.web.planner.allocation.streches; + + ... + @Service @Scope(BeanDefinition.SCOPE_PROTOTYPE) @OnConcurrentModification(goToPage = "/planner/stretchesFunctionTemplate.zul") @@ -1156,7 +1164,7 @@ folder: ... } -As you can see model is a Spring bean, in order that controller communicates +As you can see, model is a Spring bean, in order that controller communicates with model, you need to do 2 different things: * Add the following line at ``.zul`` page (this is not really needed because of @@ -1214,9 +1222,9 @@ example. return stretchesFunctionTemplateDAO.getAll(); } -As you can see method ``getStretchesFunctionTemplates`` in model is not involved -in conversation protocol. Moreover, you will also need to implement ``getAll`` -method in DAO that would be quite simple:: +As you can see, method ``getStretchesFunctionTemplates`` in model is not +involved in conversation protocol. Moreover, you will also need to implement +``getAll`` method in DAO that would be quite simple:: @Override public List getAll() { @@ -1414,7 +1422,7 @@ model:: stretchesFunctionTemplate = null; } -As you can see you need to use ``@Transactional`` annotation in ``confirmSave`` +As you can see, you need to use ``@Transactional`` annotation in ``confirmSave`` method. This is needed in order to access DAO object inside Hibernate session in order to store entity on database. If you just need to query data you should mark transaction as read only (``@Transactional(readOnly = true)``). @@ -1516,6 +1524,10 @@ test for your DAO. Simply create the following file called :: + package org.navalplanner.business.test.planner.daos; + + ... + @Transactional @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE, @@ -1546,7 +1558,7 @@ test for your DAO. Simply create the following file called } -As you can see you need some Spring annotations to run test inside a Spring +As you can see, you need some Spring annotations to run test inside a Spring context in order to be able to use ``@Autowired`` for different Spring beans, in that case the DAO class. @@ -1584,8 +1596,8 @@ Then you could add the following test to check that dao.save(stretchesFunctionTemplate); } -As you can see here it is being checked that a ``ValidationException`` is thrown -when it is trying to store an entity with empty name. +As you can see here, it is being checked that a ``ValidationException`` is +thrown when it is trying to store an entity with empty name. .. NOTE:: @@ -1772,6 +1784,574 @@ In order to do not allow users send wrong data to server you should use Web services ============ +NavalPlan provides web services as integration tool for third party applications +that want to get/send data from/to NavalPlan. For this task NavalPlan +implementation is based in REST_ (Representational State Transfer) services with +the following behaviour: + +* All integration entities will have a code that will allow them to be + identified for both NavalPlan and third party application. It is important to + stress that this ``code`` attribute will be different to Hibernate ``id`` + attribute, which is an internal identifier for the database and could be + repeated in different instances of NavalPlan. + +* When NavalPlan receive an entity via web service, it follows the next steps: + + * Check if entity already exists on database. Using ``code`` attribute for + this. + * If entity does not exist, then it is created the new entity and stored on + database. + * If entity already exists, then it is modified and stored on database. + +* Delete operation is not going to be allowed, because of remove some entity + could take side effects in other schedules done in NavalPlan. Anyway, it is + possible that some entities provide an attribute to deactivate them in the + system, this could be changed with a modification operation. + +NavalPlan entities will be represented as XML files in order to be sent or +received as web service data. + +Convert into ``IntegrationEntity`` +---------------------------------- + +A lot of entities in NavalPlan can be considered integration entities, e.i. +suitable entities to be sent/received to/from other applications. As this is a +common case a new class ``IntegrationEntity`` was defined and all these entities +extends this class instead of ``BaseEntity``. Actually, ``IntegrationEntity`` +extends in turn ``BaseEntity``. + +For example, as part of this exercise you are going to become +``StretchesFunctionTemplate`` in an integration entity. Even when it could not +have be really needed for the moment, it is usueflu as a test case in order to +know how to develop a web service in NavalPlan. + +First of all, you need to make that ``StretchesFunctionTemplate`` inherits from +``IntegrationEntity``:: + + public class StretchesFunctionTemplate extends IntegrationEntity { + ... + } + +This fact means that ``StretchesFunctionTemplate`` entity has a new attribute +called ``code``. Thus, you will need to modify Hibernate mapping in +``ResourceAllocations.hbm.xml`` file in order to add the following line:: + + + +And you will need to add a new changeset to Liquibase changelog in +``db.changelog-1.0.xml`` file:: + + + Add new column code in table stretches_function_template with not-null constraint + + + + + + +.. WARNING:: + + This Liquibase changeset is just an example and should not be used as is in + the real world. The reason is that if there are already + ``StretchesFunctionTemplate`` entities stored in database, this changeset is + setting ``code`` attribute to empty, which is wrong as code should be unique. + This should be fixed using some kind of custom refactorization provided by + Liquibase. + +``IntegrationEntity`` is an abstract class, thus you need to override abstract +method ``getIntegrationEntityDAO``. This method should return DAO of this +entity, that will be used to check that code is not repeated when entity is +validated. + +However, before implementing this method you need to modify entity DAO to extend +``IntegrationEntityDAO``. This provides implementation for several methods in +order to check constraints related with ``code`` field. For this you will need +to modify both interface and DAO implementation:: + + public interface IStretchesFunctionTemplateDAO extends + IIntegrationEntityDAO { + ... + } + + public class StretchesFunctionTemplateDAO extends + IntegrationEntityDAO implements + IStretchesFunctionTemplateDAO { + ... + } + +It is very convenient to use these common classes as you will have a lot of +functionalities automatically added to your entity. Now, you are ready to +implement ``getIntegrationEntityDAO`` in the entity. Just one more problem, you +need to know how to access DAO from an entity, when entities are not in a Spring +context. For this purpose a class called ``Registry`` exists in NavalPlan, so +before modifying entity you will add the following lines to ``Registry``:: + + @Autowired + private IStretchesFunctionTemplateDAO stretchesFunctionTemplateDAO; + + public static IStretchesFunctionTemplateDAO getStretchesFunctionTemplateDAO() { + return getInstance().stretchesFunctionTemplateDAO; + } + +And then you will override ``getIntegrationEntityDAO`` in +``StretchesFunctionTemplate``:: + + @Override + protected IStretchesFunctionTemplateDAO getIntegrationEntityDAO() { + return Registry.getStretchesFunctionTemplateDAO(); + } + +At this moment, your entity ``StretchesFunctionTemplate`` is an integration +entity, so it is valid to implement a web service providing import and export +facilities for this entity. + +.. NOTE:: + + Integration entities usually will show ``code`` attribute in the interface, in + order that users could uniquely reference to one entity. Moreover, this code + usually follows some kind of sequence prefixed with entity name, these + sequences are managed in *Configuration* window at NavalPlan. + + So, if you want ``StretchesFunctionTemplate`` entity will be a common + integration entity in NavalPlan you will need to do something similar to + other entities: + + * Add your entity in ``EntityNameEnum``. + * Modify *Configuration* window in order to allow manage the new sequence for + your entity. + * Reuse ``IIntegrationEntityModel`` and ``IntegrationEntityModel`` in your + model. These will provide standard methods to generate entity sequence. + + For the moment, as it is not really necessary for this exercise, this part + will be omitted in this document. + +Implement export web service +---------------------------- + +Now you are going to implement the export service for +``StretchesFunctionTemplate`` entity. Thanks to this service, third party +applications could access to the list of ``StretchesFunctionTemplate`` defined +in NavalPlan. Web services classes are under +``navalplanner-webapp/src/main/java/org/navalplanner/ws/`` folder, inside it you +should create a new directory ``stretchesfunctiontemplates`` with 2 +subdirectories ``api`` and ``impl``. + +Again, like in previous point, there are some classes already defined which +provide main functionality needed to implement the web service. You will extends +those classes throughout the sample. + +Defining service interface +.......................... + +First of all, you will create an interface inside ``api`` folder. This interface +will define a method to export all ``StretchesFunctionTemplate`` entities stored +in NavalPlan database:: + + package org.navalplanner.ws.stretchesfunctiontemplates.api; + + ... + + public interface IStretchesFunctionTemplateService { + + public StretchesFunctionTemplateListDTO getStretchesFunctionTemplates(); + + } + +Mapping between entities and XMLs +................................. + +As you can see, web service interface uses a DTO_ (Data Transfer Object) class, +as you do not need to export all the business logic managed by entities you will +create lighter classes (DTOs) in order to export and import data associated with +web services. + +Then you are going to define all DTOs (inside ``api`` folder), needed for +``StretchesFunctionTemplate`` entity. You will need three DTOs: + +* ``StretchesFunctionTemplateListDTO``: + +:: + + package org.navalplanner.ws.stretchesfunctiontemplates.api; + + ... + + @XmlRootElement(name = "stretches-function-template-list") + public class StretchesFunctionTemplateListDTO { + + @XmlElement(name = "stretches-function-template") + public List stretchesFunctionTemplateDTOs = new ArrayList(); + + public StretchesFunctionTemplateListDTO() { + } + + public StretchesFunctionTemplateListDTO( + List stretchesFunctionTemplateDTOs) { + this.stretchesFunctionTemplateDTOs = stretchesFunctionTemplateDTOs; + } + + } + +* ``StretchesFunctionTemplateDTO``: + +:: + + package org.navalplanner.ws.stretchesfunctiontemplates.api; + + ... + + public class StretchesFunctionTemplateDTO extends IntegrationEntityDTO { + + public final static String ENTITY_TYPE = "stretches-function-template"; + + @XmlAttribute + public String name; + + @XmlElementWrapper(name = "stretches-list") + @XmlElement(name = "stretch-template") + public List stretches = new ArrayList(); + + public StretchesFunctionTemplateDTO() { + } + + public StretchesFunctionTemplateDTO(String code, String name, + List stretches) { + super(code); + this.name = name; + this.stretches = stretches; + + } + + public StretchesFunctionTemplateDTO(String name, + List stretches) { + this(generateCode(), name, stretches); + } + + @Override + public String getEntityType() { + return ENTITY_TYPE; + } + + } + +* ``StretchesFunctionTemplateDTO``: + +:: + + package org.navalplanner.ws.stretchesfunctiontemplates.api; + + ... + + public class StretchTemplateDTO { + + @XmlAttribute(name = "duration-proportion") + public BigDecimal durationProportion; + + @XmlAttribute(name = "progress-proportion") + public BigDecimal progressProportion; + + public StretchTemplateDTO() { + } + + public StretchTemplateDTO(BigDecimal durationProportion, + BigDecimal progressProportion) { + this.durationProportion = durationProportion; + this.progressProportion = progressProportion; + } + + } + +In these classes you can see that NavalPlan uses JAXB_ for XML bindings. This +makes really easy mapping between Java classes and XML representations providing +annotations like ``@XmlAttribute``, ``@XmlElement``, etc. + +Moreover, you also need a file called ``package-info.java`` in ``api`` folder in +order to define namespace for REST service. This file will have the following +content:: + + @javax.xml.bind.annotation.XmlSchema( + elementFormDefault=javax.xml.bind.annotation.XmlNsForm.QUALIFIED, + namespace=WSCommonGlobalNames.REST_NAMESPACE + ) + package org.navalplanner.ws.stretchesfunctiontemplates.api; + import org.navalplanner.ws.common.api.WSCommonGlobalNames; + +Extending ``GenericRESTService`` +................................ + +Then you are going to implement web service interface with a new class which +will extend ``GenericRESTService``. This class provides generic stuff for +implementing new REST services. So, you are going to create the following class +inside ``impl`` folder this time:: + + package org.navalplanner.ws.stretchesfunctiontemplates.impl; + + ... + + @Path("/stretchesfunctiontemplates/") + @Produces("application/xml") + @Service("stretchesFunctionTemplateServiceREST") + public class StretchesFunctionTemplateServiceREST extends + GenericRESTService + implements IStretchesFunctionTemplateService { + + @Autowired + private IStretchesFunctionTemplateDAO dao; + + @Override + protected IIntegrationEntityDAO getIntegrationEntityDAO() { + return dao; + } + + @Override + protected StretchesFunctionTemplateDTO toDTO( + StretchesFunctionTemplate entity) { + return StretchesFunctionTemplateConverter.toDTO(entity); + } + + @Override + protected StretchesFunctionTemplate toEntity( + StretchesFunctionTemplateDTO entityDTO) throws ValidationException, + RecoverableErrorException { + // Not needed for export service + return null; + } + + @Override + protected void updateEntity(StretchesFunctionTemplate entity, + StretchesFunctionTemplateDTO entityDTO) throws ValidationException, + RecoverableErrorException { + // Not needed for export service + } + + @Override + @GET + @Transactional(readOnly = true) + public StretchesFunctionTemplateListDTO getStretchesFunctionTemplates() { + return new StretchesFunctionTemplateListDTO(findAll()); + } + + } + +Let's split this file in small hunks in order to explain different annotations. +Take into account that NavalPlan uses JAX-RS_ (Java API for RESTful Web +Services) to create web services. + +:: + + @Path("/stretchesfunctiontemplates/") + +It is a JAX-RS annotation to indicates the URI for the web service. In NavalPlan +it usually is the entity name in lowercase and plural. + +:: + + @Produces("application/xml") + +Another JAX-RS annotation which indicates the media type for a method. In this +case it is used at class level, which means that all methods for this web service +produce XML results. This is true in NavalPlan, as even methods to import data +will return an XML with possible errors or a success message. + +:: + + @Service("stretchesFunctionTemplateServiceREST") + +In this case it is a Spring annotation which indicates that the class is a +service. Then you will need to add it in +``navalplanner-webapp-spring-config.xml`` file:: + + + ... + + + +:: + + public class StretchesFunctionTemplateServiceREST extends + GenericRESTService + implements IStretchesFunctionTemplateService { + +As you can see, new class extends ``GenericRESTService`` an abstract class that +provides common functionality for web services. It also implements web service +interface, where you indicate methods provided by this web service. + +:: + + @Autowired + private IStretchesFunctionTemplateDAO dao; + +Like this class is marked as a Spring service, you could use ``@Autowired`` +annotation to inject DAO class for this entity. + +:: + + @Override + protected IIntegrationEntityDAO getIntegrationEntityDAO() { + return dao; + } + +This is an abstract method that you need to implement, it simply returns DAO +class for ``StretchesFunctionTemplate`` entity. + +:: + + @Override + protected StretchesFunctionTemplateDTO toDTO( + StretchesFunctionTemplate entity) { + return StretchesFunctionTemplateConverter.toDTO(entity); + } + +Another abstract method overridden, in this case it should create a DTO from an +entity. As you can see it delegates the conversion in a special class +``StretchesFunctionTemplateConverter``. + +:: + + @Override + protected StretchesFunctionTemplate toEntity( + StretchesFunctionTemplateDTO entityDTO) throws ValidationException, + RecoverableErrorException { + // Not needed for export service + return null; + } + +Similar to previous method, but on the other way around. This will create an +entity from a DTO. This is used when you are implementing an import service and +you receive new entities. Again, it usually delegates in a converter class. + +:: + + @Override + protected void updateEntity(StretchesFunctionTemplate entity, + StretchesFunctionTemplateDTO entityDTO) throws ValidationException, + RecoverableErrorException { + // Not needed for export service + } + +This will be used when you receive an already existent entity in order to update +it from a DTO. Like previous ones, it usually delegates in a converter class. + +:: + + @Override + @GET + @Transactional(readOnly = true) + public StretchesFunctionTemplateListDTO getStretchesFunctionTemplates() { + return new StretchesFunctionTemplateListDTO(findAll()); + } + +Finally, this is the implementation for the only method provided by web service +interface, which will export all ``StretchesFunctionTemplate`` stored in +NavalPlan. Method is marked with ``@GET`` JAX-RS annotation, which indicates +that current method will respond to HTTP ``GET`` requests. Moreover, it is also +needed open a transaction, as it is going to use DAO in ``findAll`` method +implemented by ``GenericRESTService``. + +Converting entities to/from DTOs +................................ + +The last step will be implement the converter class. In this case you will just +need to implement the method to convert from ``StretchesFunctionTemplate`` +entity to a DTO. + +This is a simply class that will be inside ``impl`` folder and will have the +following content:: + + package org.navalplanner.ws.stretchesfunctiontemplates.impl; + + ... + + public final class StretchesFunctionTemplateConverter { + + private StretchesFunctionTemplateConverter() { + } + + public final static StretchesFunctionTemplateDTO toDTO( + StretchesFunctionTemplate stretchesFunctionTemplate) { + // Convert stretches + List stretchTemplateDTOs = new ArrayList(); + for (StretchTemplate each : stretchesFunctionTemplate.getStretches()) { + stretchTemplateDTOs.add(toDTO(each)); + } + + return new StretchesFunctionTemplateDTO(stretchesFunctionTemplate + .getCode(), stretchesFunctionTemplate.getName(), stretchTemplateDTOs); + } + + private static StretchTemplateDTO toDTO(StretchTemplate stretchTemplate) { + return new StretchTemplateDTO(stretchTemplate.getDurationProportion(), + stretchTemplate.getProgressProportion()); + } + + } + +Now you are ready to test your web service. If you go to this URL +http://localhost:8080/navalplanner-webapp/ws/rest/stretchesfunctiontemplates/, +and you login with an user that has permission to access web services (e.g. user +``wsreader`` with password ``wsreader``) you will get a XML with +``StretchesFunctionTemplate`` stored in NavalPlan. + +.. NOTE:: + + It is recommended to define some JUnit test for each web service in order to + validate if they are working properly. In this case is even more important as + developers could not take into account web services when they are changing + some entities in the business logic. Thanks to this tests developers will detect + problems in the exactly moment that they are adding the changes. + + You can take a look to other already existent entities in order to create your + test for the new one that will be called + ``StretchesFunctionTemplateServiceTest``. + +Web services scripts +-------------------- + +Export services are easily tested just accessing URL with any web browser. +However, in the case of import services you will need to use HTTP ``POST`` +method in order to test them. For this reason some convenient scripts were +created in NavalPlan repository in ``scripts/rest-clients`` directory. + +.. NOTE:: + + Currently these scripts depends on Tidy and Ruby to be installed in your + system. You could install them in a Debian based distribution with the + following command as root:: + + apt-get install tidy ruby + +Then for this example you will create a script called +``export-stretches-function-templates.sh``, that will be very similar to the +rest of export scripts but changing web service path:: + + #!/bin/sh + + . ./rest-common-env.sh + + printf "Login name: " + read loginName + printf "Password: " + read password + + if [ "$1" = "--prod" ]; then + baseServiceURL=$PRODUCTION_BASE_SERVICE_URL + certificate=$PRODUCTION_CERTIFICATE + else + baseServiceURL=$DEVELOPMENT_BASE_SERVICE_URL + certificate=$DEVELOPMENT_CERTIFICATE + fi + + authorization=`./base64.sh $loginName:$password` + + curl -sv -X GET $certificate --header "Authorization: Basic $authorization" \ + $baseServiceURL/stretchesfunctiontemplates | tidy -xml -i -q -utf8 + +Script will request you user and password in order to access to web service, so +you could use ``wsreader`` user to check that it works properly. + Conclusion ========== @@ -1809,6 +2389,11 @@ NavalPlan uses testing framework JUnit. Moreover, Hibernate Validator is used to validate business logic. Business logic is always tested and validated in model layer, where entities are responsible to validate their own data. +Finally, you created an export web service for the new entity. NavalPlan +services uses XML APIs provided by Java (like JAXB and JAX-RS) which make easier +developing new web services. For each web service, some test scripts are +provided in order to check implementation. + .. _CRUD: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete .. _NavalPlan: http://www.navalplan.org/en/ @@ -1825,3 +2410,7 @@ layer, where entities are responsible to validate their own data. .. _JUnit: http://junit.sourceforge.net/ .. _TDD: http://en.wikipedia.org/wiki/Test_driven_development .. _`Hibernate Validator`: http://www.hibernate.org/subprojects/validator.html +.. _REST: http://en.wikipedia.org/wiki/Representational_State_Transfer +.. _DTO: http://en.wikipedia.org/wiki/Data_Transfer_Object +.. _JAXB: http://en.wikipedia.org/wiki/Java_Architecture_for_XML_Binding +.. _JAX-RS: http://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services