Wednesday, 21 September 2016

CronJob Real time example

Requirement:

We need to run some task which should remove the products whose price is not defined and it is X days old in the system.
Now our business logic is to remove the products if its price is not defined and it is X no of days old.
Steps for the business logic
Load the Products which are X days old
Check if the product has price from the above product list
If the price is not defined , delete the product

Step 1

Since we need to add new configuration property called X days as dynamic value to the Cron Job, we need to define a new CronJob Item type
Define new Cron Job item type in items.xml as below
hybris\bin\custom\training\trainingcore\resources\trainingcore-items.xml

<itemtype code="ProductsRemovalCronJob" extends="CronJob" jaloclass="de.hybris.platform.cronjob.jalo.ProductsRemovalCronJob"
                  autocreate="true" generate="true">
            <attributes>
                <attribute qualifier="xDaysOld" type="int">
                    <modifiers read="true" write="true" optional="false"/>
                    <defaultvalue>Integer.valueOf(10)</defaultvalue>
                    <description>All Products older than this value in days and whose price is not defined will be removed
                    </description>
                    <persistence type="property"/>
                </attribute>
            </attributes>
        </itemtype>
Defined a new Cron Job item type ProductsRemovalCronJob which extends CronJob item type
So all the attributes of CronJob item type are also inherited to this new Cron Job item type.
Do ant all and refresh the platform folder to check ProductsRemovalCronJobModel.java is generated

Step 2

Create a class which acts as a Job by extending AbstractJobPerformable class and override the perform() method to have our business logic.
Created the class inside our custom core extension as below
 
package org.training.core.jobs;
 
import de.hybris.platform.cronjob.enums.CronJobResult;
import de.hybris.platform.cronjob.enums.CronJobStatus;
import de.hybris.platform.cronjob.model.ProductsRemovalCronJobModel;
import de.hybris.platform.servicelayer.cronjob.AbstractJobPerformable;
import de.hybris.platform.servicelayer.cronjob.PerformResult;
 
 
public class ProductsRemovalJob extends AbstractJobPerformable<ProductsRemovalCronJobModel>
{
    @Override
    public PerformResult perform(final ProductsRemovalCronJobModel productsRemovalCronJobModel)
    {
 
        return new PerformResult(CronJobResult.SUCCESS, CronJobStatus.FINISHED);
    }
 
}

This is a class which does not have any business logic written for the Job to perform.
Let’s define the Business logic for the same as below hybris\hybris\bin\custom\training\trainingcore\src\org\training\core\jobs\ProductsRemovalJob.java
package org.training.core.jobs;
 
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.cronjob.enums.CronJobResult;
import de.hybris.platform.cronjob.enums.CronJobStatus;
import de.hybris.platform.cronjob.model.ProductsRemovalCronJobModel;
import de.hybris.platform.servicelayer.cronjob.AbstractJobPerformable;
import de.hybris.platform.servicelayer.cronjob.PerformResult;
import de.hybris.platform.servicelayer.exceptions.ModelRemovalException;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.site.BaseSiteService;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfigService;
import de.hybris.platform.solrfacetsearch.config.exceptions.FacetConfigServiceException;
import de.hybris.platform.solrfacetsearch.indexer.IndexerService;
import de.hybris.platform.solrfacetsearch.indexer.exceptions.IndexerException;
import de.hybris.platform.solrfacetsearch.model.config.SolrFacetSearchConfigModel;
import de.hybris.platform.tx.Transaction;
 
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
 
import org.apache.log4j.Logger;
import org.springframework.util.CollectionUtils;
import org.training.core.dao.CustomProductsDAO;
 
 
public class ProductsRemovalJob extends AbstractJobPerformable<ProductsRemovalCronJobModel>
{
    public static final String SITE_UID = "apparel-uk";
    private CustomProductsDAO customProductsDao;
    private ModelService modelService;
    private IndexerService indexerService;
    private FacetSearchConfigService facetSearchConfigService;
    private BaseSiteService baseSiteService;
 
    private final static Logger LOG = Logger.getLogger(ProductsRemovalJob.class.getName());
 
    @Override
    public PerformResult perform(final ProductsRemovalCronJobModel productsRemovalCronJobModel)
    {
        final int noOfDaysOldToRemove = productsRemovalCronJobModel.getXDaysOld();
        final Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, -noOfDaysOldToRemove);
        final Date oldDate = cal.getTime();
        final List<ProductModel> productModelListToBeDeleted = new ArrayList<>();
        final List<ProductModel> productModelList = customProductsDao.findAllProductsOlderThanSpecifiedDays(oldDate);
        LOG.debug("Products older than specified days size:" + productModelList.size());
        for (final ProductModel productModel : productModelList)
        {
            if (CollectionUtils.isEmpty(productModel.getEurope1Prices()))
            {
                productModelListToBeDeleted.add(productModel);
            }
        }
 
        if (!CollectionUtils.isEmpty(productModelListToBeDeleted))
        {
            Transaction tx = null;
            try
            {
                tx = Transaction.current();
                tx.begin();
                getModelService().removeAll(productModelListToBeDeleted);
                tx.commit();
            }
            catch (final ModelRemovalException e)
            {
                if (null != tx)
                {
                    tx.rollback();
                }
                LOG.error("Could not remove the product list -->" + e);
            }
        }
 
        try
        {
            final SolrFacetSearchConfigModel facetSearchConfigModel = baseSiteService.getBaseSiteForUID(SITE_UID)
                    .getSolrFacetSearchConfiguration();
            final FacetSearchConfig facetSearchConfig = facetSearchConfigService.getConfiguration(facetSearchConfigModel.getName());
            indexerService.performFullIndex(facetSearchConfig);
        }
        catch (final FacetConfigServiceException | IndexerException e)
        {
            LOG.error("Could not index the products -->" + e);
        }
 
        return new PerformResult(CronJobResult.SUCCESS, CronJobStatus.FINISHED);
    }
 
    public CustomProductsDAO getCustomProductsDao()
    {
        return customProductsDao;
    }
 
    public void setCustomProductsDao(final CustomProductsDAO customProductsDao)
    {
        this.customProductsDao = customProductsDao;
    }
 
    public ModelService getModelService()
    {
        return modelService;
    }
 
    @Override
    public void setModelService(final ModelService modelService)
    {
 
        this.modelService = modelService;
    }
 
    
    public IndexerService getIndexerService()
    {
        return indexerService;
    }
 
    
    public void setIndexerService(final IndexerService indexerService)
    {
        this.indexerService = indexerService;
    }
 
    
    public FacetSearchConfigService getFacetSearchConfigService()
    {
        return facetSearchConfigService;
    }
 
    
    public void setFacetSearchConfigService(final FacetSearchConfigService facetSearchConfigService)
    {
        this.facetSearchConfigService = facetSearchConfigService;
    }
 
    public BaseSiteService getBaseSiteService()
    {
        return baseSiteService;
    }
 
    public void setBaseSiteService(final BaseSiteService baseSiteService)
    {
        this.baseSiteService = baseSiteService;
    }
 
}

We have written the business logic which gets all the products older than specified days
Then we are iterating the product List and checking each product whether they have price row or not, If price row is not defined then we are adding such products to a list of deleting products.
then we are removing all such products.
After that we are calling the Solr full indexing to index only those products which have the price.
Since we need to define the query to get all the products older than specified days, we will create a new DAO Implementation class and interface as below
CustomProductsDAO.java
hybris\hybris\bin\custom\training\trainingcore\src\org\training\core\dao\CustomProductsDAO.java
package org.training.core.dao;   import de.hybris.platform.core.model.product.ProductModel; import java.util.Date; import java.util.List;   public interface CustomProductsDAO {     public List<ProductModel> findAllProductsOlderThanSpecifiedDays(final Date oldDate); }
CustomProductsDAOImpl.java
hybris\hybris\bin\custom\training\trainingcore\src\org\training\core\dao\impl\CustomProductsDAOImpl.java

package org.training.core.dao.impl;
 
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import de.hybris.platform.servicelayer.search.SearchResult;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.training.core.dao.CustomProductsDAO;
 
 
public class CustomProductsDAOImpl implements CustomProductsDAO
{
 
    private FlexibleSearchService flexibleSearchService;
 
    @Override
    public List<ProductModel> findAllProductsOlderThanSpecifiedDays(final Date oldDate)
    {
        final StringBuilder query = new StringBuilder("SELECT {pk} FROM {Product} WHERE {creationtime}<=?oldDate");
    final Map<String, Object> params = new HashMap<String, Object>();
        params.put("oldDate", oldDate);
 
        final FlexibleSearchQuery searchQuery = new FlexibleSearchQuery(query.toString());
        searchQuery.addQueryParameters(params);
        searchQuery.setResultClassList(Collections.singletonList(ProductModel.class));
        final SearchResult searchResult = getFlexibleSearchService().search(searchQuery);
        return searchResult.getResult();
    }
 
    
    public FlexibleSearchService getFlexibleSearchService()
    {
        return flexibleSearchService;
    }
 
    
    public void setFlexibleSearchService(final FlexibleSearchService flexibleSearchService)
    {
        this.flexibleSearchService = flexibleSearchService;
    }
 
}


Step 3

Define the above classes as spring beans in the *-spring.xml file as below hybris\bin\custom\training\trainingcore\resources\trainingcore-spring.xml
<bean id="customProductsDao" class="org.training.core.dao.impl.CustomProductsDAOImpl">
        <property name="flexibleSearchService" ref="flexibleSearchService"/>
    </bean>
    
    <bean id="productsRemovalJob" class="org.training.core.jobs.ProductsRemovalJob"
        parent="abstractJobPerformable">
        <property name="customProductsDao" ref="customProductsDao" />
        <property name="modelService" ref="modelService" />
        <property name="indexerService" ref="indexerService" />
        <property name="facetSearchConfigService" ref="facetSearchConfigService" />
        <property name="baseSiteService" ref="baseSiteService"/>
    </bean>

Step 4

Write an impex to create a cron job instance as below hybris\bin\custom\training\trainingcore\resources\impex\essentialdataJobs.impex
$siteUid=apparel-uk
INSERT_UPDATE ProductsRemovalCronJob; code[unique=true];job(code);xDaysOld;sessionLanguage(isocode);sessionCurrency(isocode);sites(uid)[default=$siteUid]
;productsRemovalCronJob;productsRemovalJob;5;en;EUR
We are specifying old days as 5 so that 5 days older products will be considered.

Step 5

Write a impex into the same file to create a Trigger instance as below
INSERT_UPDATE Trigger;cronjob(code)[unique=true];active;cronExpression
#Fires at 10:15 AM everyday
;productsRemovalCronJob;true;0 15 10 * * ?

Look at the relation between CronJob,Job and Trigger here.
While inserting ProductsRemovalCronJob Cron Job we are giving the reference of Job which should be same as spring bean id of the Job.
While inserting Trigger, we are giving the reference of ProductsRemovalCronJob which should be same as code defined while insertingProductsRemovalCronJob
Also we have used Cron Expression in the Trigger which schedules it to run every day at 10:15 AM

Step 6

Do Update System by selecting the extension where we defined our job Now Cron Job will be executed at the scheduled time automatically.
Note: After Updating the system,we can run the below query to check the ServiceLayerJob created for the Job that we defined as a spring bean
select * from {servicelayerjob} where {code} = 'productsRemovalJob'
We should be able to see the Job details if everything is fine.

Step 7

Check the Products in HMC whose price not defined and they are X days old before Job runs
Search the same products after the Job runs successfully
We should not be able to see those products now.
See below screenshot which shows products only those which have prices and all other products have been deleted.

How to Configure the CronJob manually ?

We can configure and run the cron job through HMC whenever we want to do it manually.
Steps for the same are as below
1)Log in to HMC 2)Go to System -> CronJobs 3)Type productsRemovalCronJob in Value Text Box for code then click on search 4)open productsRemovalCronJob 5) Go to Time Schedule tab ->set time in (Trigger text box) then save it.
If we want to start it immediately then Click Start CronJob now then our job will perform and display popup box with CronJob performed Result

How to execute the CronJob through Code rather than through Trigger ?

We can also execute the Cron Job through code rather than running it through Triggers as below
ProductsRemovalCronJobModel productsRemovalCronJobModel =modelService.create(ProductsRemovalCronJobModel.class); // assign Job to CronJob ServicelayerJobModel servicelayerJobModel = modelService.create(ServicelayerJobModel.class); servicelayerJobModel.setActive(true); servicelayerJobModel.setSpringId("productsRemovalJob"); productsRemovalCronJobModel.setJob(servicelayerJobModel); modelService.save(productsRemovalCronJobModel); cronJobService.performCronJob(productsRemovalCronJobModel);
In the above code, we have written a code to call the Cron job through CronJobService’s performCronJob() method.