Hybris: About SynchronizationService
Intro
As we may need or want, using SynchronizationService in Hybris 5.X may not work instantly by injection. That happens because this class is declared and used mainly in cockpits. A simple search may point that this interface (and its SynchronizationServiceImpl) is part of the cockpitserver.jar that is a dependency of cockpit extension, and not the Hybris Platform itself. So, how can I use this service for synchronizing some specific item in an random extension, which is not a cockpit extension?
Scenario
Imagine a personalized workflow that was requested by a stakeholder, aiming the control of the approval of an item (mainly cmspages) which holds several steps, including the approval of a page itself and the synchronization with the online catalog – the content catalog.
To perform such action, we may need to use the SynchronizationService instead of going under jalo layer and touching the lava.
A possible solution
A possible solution for the task is to create a class that implements the AutomatedWorkflowTemplateJob interface, which is the interface that Hybris requires to perform an action when a decision is made inside a workflow. Note that there are others interfaces that may also be required by Hybris for this feature, but we will focus on this one.
Then, after the class is built, it needs to be registered as a spring bean so it can be retrieved by Hybris and perform the action as intended.
Here, we need to remember that a workflow item is created by a base workflow template item, that will have actions (and the workflow itself will have those actions too) and those actions have decisions that have a Handler Spring Bean Id. This id is the bean id registered for the built class mentioned before. So, briefly, when a user decides something about the workflow, a step, he makes a decision. This decision triggers the action coded inside the built class.
Coding
First of all, lets list our implemented class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | package com.gregoreki.approvalflow.action; import de.hybris.platform.cms2.model.pages.AbstractPageModel; import de.hybris.platform.cockpit.services.meta.TypeService; import de.hybris.platform.core.model.ItemModel; import de.hybris.platform.servicelayer.model.ModelService; import de.hybris.platform.workflow.jobs.AutomatedWorkflowTemplateJob; import de.hybris.platform.workflow.model.WorkflowActionModel; import de.hybris.platform.workflow.model.WorkflowDecisionModel; import de.hybris.platform.catalog.CatalogVersionService; import de.hybris.platform.catalog.model.CatalogVersionModel; import de.hybris.platform.cms2.enums.CmsApprovalStatus; import de.hybris.platform.cockpit.events.impl.ItemChangedEvent; import de.hybris.platform.cockpit.services.sync.SynchronizationService; import de.hybris.platform.cockpit.session.UISessionUtils; import de.hybris.platform.core.Registry; import java.util.List; import org.zkoss.zkplus.spring.SpringUtil; import com.gregoreki.approvalflow.facade.ApprovalFlowFacade; /** * @author c.gregoreki */ public abstract class OnPageApprovalJob implements AutomatedWorkflowTemplateJob { /* * Injections */ private ModelService modelService; private ApprovalFlowFacade approvalFlowFacade; private TypeService typeService; private CatalogVersionService catalogVersionService; // SynchronizationService will not be injected. See getter and setter. private SynchronizationService synchronizationService; // implementation omitted protected AbstractPageModel getAttachedPage(final WorkflowActionModel action); /* * Getters and setters omitted */ /* * This is the perform method that will be triggered when the decision * is made. */ @Override public WorkflowDecisionModel perform(final WorkflowActionModel paramWorkflowActionModel) { AbstractPageModel page = getAttachedPage(paramWorkflowActionModel); // approve the page page.setApprovalStatus(CmsApprovalStatus.APPROVED); // persist the modification getModelService().save(page); /* * lift a global event to hybris so it knows it needs to refresh the * component on the screen with the actual data. */ UISessionUtils.getCurrentSession() .sendGlobalEvent(new ItemChangedEvent(this, getTypeService().wrapItem(page), Collections.singletonList( UISessionUtils.getCurrentSession() .getTypeService() .getPropertyDescriptor ("AbstractPage.checkStatus")), ItemChangedEvent.ChangeType.CHANGED)); /* * start the synchronization with the online catalog. */ /* * the method from SynchronizationService needs a Collection as a param. * This collection has all the items that will be synchronized. * As we only need one item inside this collection, we only add the item * we need. */ // create the collection Collection<Object> paramCollection = new ArrayList<Object>(); // add the page to the collection paramCollection.add(page); // get the target catalog version CatalogVersionModel paramCatalogVersionModel = getTargetOnlineContentCatalogVersion(page); // perform the synchronization with the synchronizationService getSynchronizationService() .performSynchronization(paramCollection, null, paramCatalogVersionModel, null); /* * return the first decision registered in to this workflow. * note that our architecture here is just an exemple and is very * simple, so it has only one decision. Get the first and only one. */ return (WorkflowDecisionModel) paramWorkflowActionModel.getDecisions().get(0); } /* * This is important! * We are not injecting the synchronizationService. We are forcing the Hybris registry to * get it with the 'getBean' method. */ protected SynchronizationService getSynchronizationService() { if (this.synchronizationService == null) { this.synchronizationService = (SynchronizationService) Registry.getApplicationContext().getBean("synchronizationService"); } return this.synchronizationService; } public void setSynchronizationService(SynchronizationService synchronizationService) { this.synchronizationService = synchronizationService; } /* * other methods omitted. */ } |
Reading the code we should notice that there are some bean injections and that the SynchronizationService will not be injected as it will be forced to be gotten by the Hybris Registry method ‘getBean’.
The perform method gets the page from the workflow object and changes the status to ‘approved’ and, then (my friend… ???), it persists the object with the ‘save’ method from modelService.
After that, a event is lifted to Hybris to refresh the component and uses synchronizationService to synchronize the item to the online catalog version.
Since the class is built, we need to link it to a spring bean, so Hybris will know that he needs to call this class to perform the action triggered by the decision.
In your extension-spring.xml, this bean needs to be created:
1 2 3 4 5 | <bean id="onPageApprovalJob" class="com.gregoreki.approvalflow.action.impl.OnPageApprovalJob"> <property name="catalogVersionService" ref="catalogVersionService" /> <property name="modelService" ref="modelService"/> <property name="approvalFlowFacade" ref="approvalFlowFacade"/> </bean> |
Now, when Hybris is deployed, it will know that the ‘onPageApprovalJob’ reference points to ‘com.gregoreki.approvalflow.action.impl.OnPageApprovalJob’ with those injections (the properties tags). Therefore, we can register the name ‘onPageApprovalJob’ inside the Handler Spring Bean Id property of the decision, inside the action, inside the workflow template.
Conclusion
As we saw in this article, a way to use the SynchronizationService without further project structure modifications is to force Hybris Registry to get it with the ‘getBean’ method. But remember, it will only work if you have the cockpit extension inside the Hybris context.