Sunday, February 7, 2016

Export and Import Data from custom Portlet



Liferay has provided an ultimate feature of exporting your data as a file of type lar. Which will contain your data which you would like to export and import to some other place for the same portlet type. Liferay has given this feature for almost all the out of box portlets and also we can implement the same for our custom portlet. Here I am implementing for my custom portlet. Here in this example i am going to show from creating an entity in Service Builder to export it from one site and again importing the data in different site.

Step: 1
            Create a service builder project. 



Step: 2 
          Define an entity inside your service.xml .

Note:  In Liferay 6.2 onwards for an Entity Eligible to Export and Import It should contain following Fields:

These fields are mandatory to any Entity to become an staged Model  , If you will add these fields then your Entity Class will Extend StagedGroupedModel. Then this model will become eligible for Staging and Export/Import. You can check you classes generated to confirm your ModelClass is extending StagedGroupedModel or not. Just go to class named ProductModel  you can see there it will be extending  StagedGroupedModel. And I would like to add that we are folloeing standard approach same as Liferay does for its out ob box portlet . you can import and export your data even if it doesn’t have these fields.


Here is how our service.xml looks

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">
<service-builder package-path="com.liferay.product.slayer">
     <author>Md Azaz Ali</author>
     <namespace>EI</namespace>

     <entity name="Product" uuid="true" local-service="true" remote-service="false">

           <!-- PK fields -->

           <column name="productId" type="long" primary="true" />

           <!-- Audit fields -->

           <column name="companyId" type="long" />
           <column name="groupId" type="long" />
           <column name="userId" type="long" />
           <column name="userName" type="String" />
           <column name="createDate" type="Date" />
           <column name="modifiedDate" type="Date" />

           <!-- Other fields -->

           <column name="productName" type="String" />
           <column name="Price" type="long" />
           <column name="sku" type="String" />

           <!-- Order -->

           <order by="asc">
                <order-column name="productName" />
           </order>
     </entity>
</service-builder>


Step: 3 
      Creating view.jsp inside folder structure



I have created a form in view.jsp and written a addProduct() method in action class ExportImportPortlet to add Products. Here is view.jsp

<%@page import="javax.portlet.ActionRequest"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://alloy.liferay.com/tld/aui" prefix="aui"%>
<portlet:defineObjects />
<portlet:actionURL var="addProductURL">
     <portlet:param name="<%=ActionRequest.ACTION_NAME %>" value="addProduct"/>
</portlet:actionURL>
<aui:form action="<%=addProductURL %>">
     <aui:input name="productName"></aui:input>
     <aui:input name="sku"></aui:input>
     <aui:input name="price"></aui:input>
     <aui:input name="submit" type="submit" value="submit"></aui:input>
</aui:form>


Here is my action Class

package com.liferay.product.portlet;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.ServiceContextFactory;
import com.liferay.product.slayer.model.Product;
import com.liferay.product.slayer.service.ProductLocalServiceUtil;
import com.liferay.util.bridges.mvc.MVCPortlet;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;

public class ExportImportPortlet extends MVCPortlet{
            public void addProduct(ActionRequest actionRequest,
                                    ActionResponse actionResponse) throws IOException, PortletException {
                        String productName = ParamUtil.getString(actionRequest, "productName");
                        String sku = ParamUtil.getString(actionRequest, "sku");
                        long price = ParamUtil.getLong(actionRequest, "price");
                        try {
                                    ServiceContext serviceContext = ServiceContextFactory.getInstance(Product.class.getName(), actionRequest);
                                    ProductLocalServiceUtil.addProduct(productName, sku, price, serviceContext);
                        } catch (Exception e) {
                                    _log.error(e);
                        }
            }
            Log _log =  LogFactoryUtil.getLog(ExportImportPortlet.class);
}
In above Action class addProduct method I am using addProduct method of ProductLocalServiceUtil which takes four parameters to add a product Information . Below is the code for method written in ProductLocalServiceImpl classs.
/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.product.slayer.service.impl;

import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.service.ServiceContext;
import com.liferay.product.slayer.model.Product;
import com.liferay.product.slayer.service.base.ProductLocalServiceBaseImpl;

import java.util.Date;

/**
 * The implementation of the product local service.
 *
 * <p>
 * All custom service methods should be put in this class. Whenever methods are added, rerun ServiceBuilder to copy their definitions into the {@link com.liferay.product.slayer.service.ProductLocalService} interface.
 *
 * <p>
 * This is a local service. Methods of this service will not have security checks based on the propagated JAAS credentials because this service can only be accessed from within the same VM.
 * </p>
 *
 * @author Md Azaz Ali
 * @see com.liferay.product.slayer.service.base.ProductLocalServiceBaseImpl
 * @see com.liferay.product.slayer.service.ProductLocalServiceUtil
 */
public class ProductLocalServiceImpl extends ProductLocalServiceBaseImpl {
            /*
             * NOTE FOR DEVELOPERS:
             *
             * Never reference this interface directly. Always use {@link com.liferay.product.slayer.service.ProductLocalServiceUtil} to access the product local service.
             */
            public Product addProduct(String productName, String sku, long price, ServiceContext serviceContext) throws SystemException {
                        Product product = productPersistence.create(counterLocalService.increment(Product.class.getName()));
                        product.setGroupId(serviceContext.getScopeGroupId());
                        product.setCompanyId(serviceContext.getCompanyId());
                        product.setUserId(serviceContext.getUserId());
                        product.setUserName(userLocalService.fetchUser(serviceContext.getUserId()).getFullName());
                        product.setCreateDate(new Date());
                        product.setModifiedDate(new Date());
                        product.setProductName(productName);
                        product.setSku(sku);
                        product.setPrice(price);
                        productLocalService.addProduct(product);
                        return product;
            }
}

Step: 4

      We will create two sites one named Export Site and add a page.

Drop portlet on page.
So add some record  like





Now add another site with name Import Site and add a page and drop same portlet there. I am not showing scrrenshot for this . you can take reference from previous one. Now we will see The steps which are responsible for Export and Import Process. Step: 5 Now we have to write a class which will extend BaseStagedModelDataHandler and it will override necessary methods. This class will be responsible for writing object to file and retrieving object from file inside exported lar. Here we have created ProductStagedModelDataHandler .


package com.liferay.product.lar;

import com.liferay.counter.service.CounterLocalServiceUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.lar.BaseStagedModelDataHandler;
import com.liferay.portal.kernel.lar.ExportImportPathUtil;
import com.liferay.portal.kernel.lar.PortletDataContext;
import com.liferay.portal.kernel.xml.Element;
import com.liferay.portal.service.ServiceContext;
import com.liferay.product.slayer.model.Product;
import com.liferay.product.slayer.service.ProductLocalServiceUtil;

import java.util.Date;

public class ProductStagedModelDataHandler extends BaseStagedModelDataHandler<Product>{

            public static final String[] CLASS_NAMES = {Product.class.getName()};
           
            @Override
            public void deleteStagedModel(String paramString1, long paramLong,
                                    String paramString2, String paramString3) throws PortalException,
                                    SystemException {
            }

            @Override
            protected void doExportStagedModel(
                                    PortletDataContext portletDataContext, Product product)
                                    throws Exception {
                        Element productElement = portletDataContext.getExportDataElement(product);
                        portletDataContext.addClassedModel(productElement, ExportImportPathUtil.getModelPath(product), product);
            }

            @Override
            protected void doImportStagedModel(
                                    PortletDataContext portletDataContext, Product product)
                                    throws Exception {
                        ServiceContext serviceContext = portletDataContext.createServiceContext(product);
                        if (portletDataContext.isDataStrategyMirror()) {
                                    Product existingProduct = ProductLocalServiceUtil.fetchProductByUuidAndGroupId(product.getUuid(), portletDataContext.getGroupId());
                                    if(existingProduct == null) {
                                                product.setProductId(CounterLocalServiceUtil.increment(Product.class.getName()));
                                                product.setGroupId(portletDataContext.getGroupId());
                                                product.setCreateDate(new Date());
                                                product.setModifiedDate(new Date());
                                                ProductLocalServiceUtil.addProduct(product);
                                    } else {
                                                existingProduct.setProductName(product.getProductName());
                                                existingProduct.setPrice(product.getPrice());
                                                existingProduct.setSku(product.getSku());
                                                ProductLocalServiceUtil.updateProduct(existingProduct);
                                    }
                        } else {
                                    product.setProductId(CounterLocalServiceUtil.increment(Product.class.getName()));
                                    product.setGroupId(portletDataContext.getGroupId());
                                    product.setCreateDate(new Date());
                                    product.setModifiedDate(new Date());
                                    ProductLocalServiceUtil.addProduct(product);
                        }
            }
            @Override
            public String getDisplayName(Product product) {
                        return product.getUuid();
            }
            @Override
            public String[] getClassNames() {
                        return CLASS_NAMES;
            }

}


Note:
In the above code we have overridden doExportStagedModel which will recive two parameters portletDataContext and product. Here portletDataContext contains all necessary methods required to export an object and product is the object to be exported.
In the above code we have overridden the method wich is returning array of classNames to be exported and imported.
Now  we have also overrided doImportStagedModel this also recieves same parameters. Here the product object is the from the lar file it means the exported one. So in our case as we are just changing the site we can just change the groupId to the current group and generate new Primarykey and add it to DB . In case you are importing it to different Server or different Instance of same Portal then you have to change company id to current companyId , both groupId and companyId of current place(where we are importing ) is available in portletDataContext object you can see.


Step:  6
            Now we have to provide the Entry of this class in liferay-portlet.xml, keep this tag just below icon tag . Any how I will provide complete file later.

<staged-model-data-handler-class>com.liferay.product.lar.ProductStagedModelDataHandler</staged-model-data-handler-class> 

Step: 7
            Now we have write another class which will extend from BasePortletDataHandler
and  have to override getExportControls , getImportControls , doExportData  and  doImportData.

package com.liferay.product.lar;

import com.liferay.portal.kernel.dao.orm.ActionableDynamicQuery;
import com.liferay.portal.kernel.dao.orm.DynamicQuery;
import com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.lar.BasePortletDataHandler;
import com.liferay.portal.kernel.lar.PortletDataContext;
import com.liferay.portal.kernel.lar.PortletDataHandlerBoolean;
import com.liferay.portal.kernel.lar.PortletDataHandlerControl;
import com.liferay.portal.kernel.lar.StagedModelDataHandlerUtil;
import com.liferay.portal.kernel.xml.Element;
import com.liferay.portlet.dynamicdatalists.model.DDLRecordSet;
import com.liferay.product.slayer.model.Product;
import com.liferay.product.slayer.service.persistence.ProductExportActionableDynamicQuery;

import java.util.List;

import javax.portlet.PortletPreferences;

public class ProductPortletDataHandler extends BasePortletDataHandler {
            public static final String NAMESPACE = "product";
            @Override
            public PortletDataHandlerControl[] getExportControls() {
                        PortletDataHandlerBoolean products = new PortletDataHandlerBoolean(NAMESPACE, "Products", true, true);
                        return new PortletDataHandlerControl[]{products};
            }
            @Override
            public PortletDataHandlerControl[] getImportControls() {
                        return getExportControls();
            }
            @Override
            protected String doExportData(PortletDataContext portletDataContext,
                                    String portletId, PortletPreferences portletPreferences)
                                    throws Exception {
                        Element rootElement = addExportDataRootElement(portletDataContext);
                        if(portletDataContext.getBooleanParameter(NAMESPACE, "Products")) {
                                    ActionableDynamicQuery productActionableDynamicQuery = getProductActionableDynamicQuery(portletDataContext);
                                    productActionableDynamicQuery.performActions();
                        }
                        return getExportDataRootElementString(rootElement);
            }
            @Override
            protected PortletPreferences doImportData(
                                    PortletDataContext portletDataContext, String portletId,
                                    PortletPreferences portletPreferences, String data)
                                    throws Exception {
                        if(portletDataContext.getBooleanParameter(NAMESPACE, "Products")) {
                                    Element productsElement =
                                                            portletDataContext.getImportDataGroupElement(
                                                                                    Product.class);
                                   
                                    List<Element> productElements = productsElement.elements();
                                   
                                    for (Element productElement : productElements) {
                                                StagedModelDataHandlerUtil.importStagedModel(
                                                                        portletDataContext, productElement);
                                    }
                                   
                        }
                        return portletPreferences;
            }
            protected ActionableDynamicQuery getProductActionableDynamicQuery(final PortletDataContext portletDataContext) throws SystemException {
                       
                        return new ProductExportActionableDynamicQuery(portletDataContext) {
                                    @Override
                                    protected void addCriteria(DynamicQuery dynamicQuery) {
                                                super.addCriteria(dynamicQuery);
                                                dynamicQuery.add(RestrictionsFactoryUtil.eq("groupId", portletDataContext.getGroupId()));
                                    }
                        };
            }

}

Note:  Here in doExportData method we are adding a root Element  after that we have created getProductActionableDynamicQuery  in which we are adding extra criteria like we have added groupId as we are going to export/import data between sites and after that we are calles performActions method which will internally get the records from db and call the doExportStagedModel in ProductStagedModelDataHandler for every object.
Now In doImportData method we are checking if Products are to be imported and getting Elements for product in which each element represents one Product object . And calling importStagedModel on ProductStagedModelDataHandler . which will import the Data.



Step 8:
            Now we have to Provide the entry of this class in liferay-portlet.xml as below . insert it just above <staged-model-data-handler-class> tag so our final liferay-portlet.xml will be.

<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.2.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_2_0.dtd">

<liferay-portlet-app>
            <portlet>
                        <portlet-name>ExportImport</portlet-name>
                        <icon>/icon.png</icon>
                        <portlet-data-handler-class>com.liferay.product.lar.ProductPortletDataHandler</portlet-data-handler-class>
                        <staged-model-data-handler-class>com.liferay.product.lar.ProductStagedModelDataHandler</staged-model-data-handler-class>
                        <header-portlet-css>/css/main.css</header-portlet-css>
                        <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
                        <css-class-wrapper>ExportImport-portlet</css-class-wrapper>
            </portlet>
            <role-mapper>
                        <role-name>administrator</role-name>
                        <role-link>Administrator</role-link>
            </role-mapper>
            <role-mapper>
                        <role-name>guest</role-name>
                        <role-link>Guest</role-link>
            </role-mapper>
            <role-mapper>
                        <role-name>power-user</role-name>
                        <role-link>Power User</role-link>
            </role-mapper>
            <role-mapper>
                        <role-name>user</role-name>
                        <role-link>User</role-link>
            </role-mapper>
</liferay-portlet-app>
Step: 9
            Now we are ready with all necessary work needed to Export and Import data from custom portlet . Now we will go to Export Site where we have droped the portlet and added some Data .Now click on gear icon on right side of portlet and click on Export/Import.


Now in below you will see following options  in Three different category Application, Content and Permissions. In content section we have Products and it is checked means it will be exported with the data if you want to skip means don’t want to export Products then you can click on checkbox and simply uncheck it.


Now to export click on export button you will see this. Export is in process…

Now  once it is done you will see this screen.


Now you can download the file and save it. Now if you will open your lar file using any zip extractor like 7 Zip you can see your  data there. Once you ope n your lar file move to

\ExportImport-201602070547.portlet.lar\group\10181\portlet\ExportImport_WAR_ExportImportportlet\10184\


You will see two folders  portlet-data.xml and portlet.xml.



You can see data exported  .

Step: 10
            Now go to import site where we have droped same portlet  Now here also go to gear icon and click on Export/Import 






Now you will see success message. So to confirm data is imported just go to DB and check the records


Here you can see highlighted records which are imported from lar file . As you can see groupId and primaryKey is different. So we are done with Export Import Feature of Liferay. For Further advanced Features you can comment on this .

2 comments:

  1. What is the best way to do if my entity refer to another object ex: a document?

    ReplyDelete
  2. Please add uuid to service.xml. Otherwise, the model won't extend StagedGroupedModel.

    ReplyDelete